From fdd3263e31f8bf352a21e05703d0a6a82c800995 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Thu, 14 Mar 2024 22:38:27 +0000 Subject: [PATCH 01/87] Separate guest/host tracking + unaligned protection (#6486) * WIP: Separate guest/host tracking + unaligned protection Allow memory manager to define support for single byte guest tracking * Formatting * Improve docs * Properly handle cases where the address space bits are too low * Address feedback --- .../Instructions/NativeInterface.cs | 20 +- src/ARMeilleure/Memory/IMemoryManager.cs | 22 + src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs | 336 +-------------- .../IVirtualMemoryManagerTracked.cs | 6 +- src/Ryujinx.Cpu/Jit/MemoryManager.cs | 152 ++++--- .../Jit/MemoryManagerHostMapped.cs | 338 +-------------- src/Ryujinx.Cpu/ManagedPageFlags.cs | 389 ++++++++++++++++++ src/Ryujinx.Graphics.Gpu/Image/Pool.cs | 2 +- src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs | 4 +- .../Memory/PhysicalMemory.cs | 16 +- src/Ryujinx.Memory/AddressSpaceManager.cs | 2 +- src/Ryujinx.Memory/IVirtualMemoryManager.cs | 3 +- src/Ryujinx.Memory/Tracking/MemoryTracking.cs | 137 ++++-- .../Tracking/MultiRegionHandle.cs | 9 +- src/Ryujinx.Memory/Tracking/RegionFlags.cs | 21 + src/Ryujinx.Memory/Tracking/RegionHandle.cs | 68 ++- src/Ryujinx.Memory/Tracking/VirtualRegion.cs | 10 +- .../MockVirtualMemoryManager.cs | 2 +- 18 files changed, 774 insertions(+), 763 deletions(-) create mode 100644 src/Ryujinx.Cpu/ManagedPageFlags.cs create mode 100644 src/Ryujinx.Memory/Tracking/RegionFlags.cs diff --git a/src/ARMeilleure/Instructions/NativeInterface.cs b/src/ARMeilleure/Instructions/NativeInterface.cs index d1b2e353c5..0cd3754f7f 100644 --- a/src/ARMeilleure/Instructions/NativeInterface.cs +++ b/src/ARMeilleure/Instructions/NativeInterface.cs @@ -91,54 +91,54 @@ namespace ARMeilleure.Instructions #region "Read" public static byte ReadByte(ulong address) { - return GetMemoryManager().ReadTracked(address); + return GetMemoryManager().ReadGuest(address); } public static ushort ReadUInt16(ulong address) { - return GetMemoryManager().ReadTracked(address); + return GetMemoryManager().ReadGuest(address); } public static uint ReadUInt32(ulong address) { - return GetMemoryManager().ReadTracked(address); + return GetMemoryManager().ReadGuest(address); } public static ulong ReadUInt64(ulong address) { - return GetMemoryManager().ReadTracked(address); + return GetMemoryManager().ReadGuest(address); } public static V128 ReadVector128(ulong address) { - return GetMemoryManager().ReadTracked(address); + return GetMemoryManager().ReadGuest(address); } #endregion #region "Write" public static void WriteByte(ulong address, byte value) { - GetMemoryManager().Write(address, value); + GetMemoryManager().WriteGuest(address, value); } public static void WriteUInt16(ulong address, ushort value) { - GetMemoryManager().Write(address, value); + GetMemoryManager().WriteGuest(address, value); } public static void WriteUInt32(ulong address, uint value) { - GetMemoryManager().Write(address, value); + GetMemoryManager().WriteGuest(address, value); } public static void WriteUInt64(ulong address, ulong value) { - GetMemoryManager().Write(address, value); + GetMemoryManager().WriteGuest(address, value); } public static void WriteVector128(ulong address, V128 value) { - GetMemoryManager().Write(address, value); + GetMemoryManager().WriteGuest(address, value); } #endregion diff --git a/src/ARMeilleure/Memory/IMemoryManager.cs b/src/ARMeilleure/Memory/IMemoryManager.cs index 952cd2b4f0..46d4426559 100644 --- a/src/ARMeilleure/Memory/IMemoryManager.cs +++ b/src/ARMeilleure/Memory/IMemoryManager.cs @@ -28,6 +28,17 @@ namespace ARMeilleure.Memory /// The data T ReadTracked(ulong va) where T : unmanaged; + /// + /// Reads data from CPU mapped memory, from guest code. (with read tracking) + /// + /// Type of the data being read + /// Virtual address of the data in memory + /// The data + T ReadGuest(ulong va) where T : unmanaged + { + return ReadTracked(va); + } + /// /// Writes data to CPU mapped memory. /// @@ -36,6 +47,17 @@ namespace ARMeilleure.Memory /// Data to be written void Write(ulong va, T value) where T : unmanaged; + /// + /// Writes data to CPU mapped memory, from guest code. + /// + /// Type of the data being written + /// Virtual address to write the data into + /// Data to be written + void WriteGuest(ulong va, T value) where T : unmanaged + { + Write(va, value); + } + /// /// Gets a read-only span of data from CPU mapped memory. /// diff --git a/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs b/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs index 6e864f4ca6..80f7c8a1f8 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; -using System.Threading; namespace Ryujinx.Cpu.AppleHv { @@ -18,21 +17,6 @@ namespace Ryujinx.Cpu.AppleHv [SupportedOSPlatform("macos")] public class HvMemoryManager : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock { - public const int PageToPteShift = 5; // 32 pages (2 bits each) in one ulong page table entry. - public const ulong BlockMappedMask = 0x5555555555555555; // First bit of each table entry set. - - private enum HostMappedPtBits : ulong - { - Unmapped = 0, - Mapped, - WriteTracked, - ReadWriteTracked, - - MappedReplicated = 0x5555555555555555, - WriteTrackedReplicated = 0xaaaaaaaaaaaaaaaa, - ReadWriteTrackedReplicated = ulong.MaxValue, - } - private readonly InvalidAccessHandler _invalidAccessHandler; private readonly HvAddressSpace _addressSpace; @@ -42,7 +26,7 @@ namespace Ryujinx.Cpu.AppleHv private readonly MemoryBlock _backingMemory; private readonly PageTable _pageTable; - private readonly ulong[] _pageBitmap; + private readonly ManagedPageFlags _pages; public bool Supports4KBPages => true; @@ -84,7 +68,7 @@ namespace Ryujinx.Cpu.AppleHv AddressSpaceBits = asBits; - _pageBitmap = new ulong[1 << (AddressSpaceBits - (PageBits + PageToPteShift))]; + _pages = new ManagedPageFlags(AddressSpaceBits); Tracking = new MemoryTracking(this, PageSize, invalidAccessHandler); } @@ -95,7 +79,7 @@ namespace Ryujinx.Cpu.AppleHv PtMap(va, pa, size); _addressSpace.MapUser(va, pa, size, MemoryPermission.ReadWriteExecute); - AddMapping(va, size); + _pages.AddMapping(va, size); Tracking.Map(va, size); } @@ -126,7 +110,7 @@ namespace Ryujinx.Cpu.AppleHv UnmapEvent?.Invoke(va, size); Tracking.Unmap(va, size); - RemoveMapping(va, size); + _pages.RemoveMapping(va, size); _addressSpace.UnmapUser(va, size); PtUnmap(va, size); } @@ -360,22 +344,7 @@ namespace Ryujinx.Cpu.AppleHv [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsMapped(ulong va) { - return ValidateAddress(va) && IsMappedImpl(va); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool IsMappedImpl(ulong va) - { - ulong page = va >> PageBits; - - int bit = (int)((page & 31) << 1); - - int pageIndex = (int)(page >> PageToPteShift); - ref ulong pageRef = ref _pageBitmap[pageIndex]; - - ulong pte = Volatile.Read(ref pageRef); - - return ((pte >> bit) & 3) != 0; + return ValidateAddress(va) && _pages.IsMapped(va); } /// @@ -383,58 +352,7 @@ namespace Ryujinx.Cpu.AppleHv { AssertValidAddressAndSize(va, size); - return IsRangeMappedImpl(va, size); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void GetPageBlockRange(ulong pageStart, ulong pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex) - { - startMask = ulong.MaxValue << ((int)(pageStart & 31) << 1); - endMask = ulong.MaxValue >> (64 - ((int)(pageEnd & 31) << 1)); - - pageIndex = (int)(pageStart >> PageToPteShift); - pageEndIndex = (int)((pageEnd - 1) >> PageToPteShift); - } - - private bool IsRangeMappedImpl(ulong va, ulong size) - { - int pages = GetPagesCount(va, size, out _); - - if (pages == 1) - { - return IsMappedImpl(va); - } - - ulong pageStart = va >> PageBits; - ulong pageEnd = pageStart + (ulong)pages; - - GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); - - // Check if either bit in each 2 bit page entry is set. - // OR the block with itself shifted down by 1, and check the first bit of each entry. - - ulong mask = BlockMappedMask & startMask; - - while (pageIndex <= pageEndIndex) - { - if (pageIndex == pageEndIndex) - { - mask &= endMask; - } - - ref ulong pageRef = ref _pageBitmap[pageIndex++]; - ulong pte = Volatile.Read(ref pageRef); - - pte |= pte >> 1; - if ((pte & mask) != mask) - { - return false; - } - - mask = BlockMappedMask; - } - - return true; + return _pages.IsRangeMapped(va, size); } private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException(); @@ -560,76 +478,7 @@ namespace Ryujinx.Cpu.AppleHv return; } - // Software table, used for managed memory tracking. - - int pages = GetPagesCount(va, size, out _); - ulong pageStart = va >> PageBits; - - if (pages == 1) - { - ulong tag = (ulong)(write ? HostMappedPtBits.WriteTracked : HostMappedPtBits.ReadWriteTracked); - - int bit = (int)((pageStart & 31) << 1); - - int pageIndex = (int)(pageStart >> PageToPteShift); - ref ulong pageRef = ref _pageBitmap[pageIndex]; - - ulong pte = Volatile.Read(ref pageRef); - ulong state = ((pte >> bit) & 3); - - if (state >= tag) - { - Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId); - return; - } - else if (state == 0) - { - ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}"); - } - } - else - { - ulong pageEnd = pageStart + (ulong)pages; - - GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); - - ulong mask = startMask; - - ulong anyTrackingTag = (ulong)HostMappedPtBits.WriteTrackedReplicated; - - while (pageIndex <= pageEndIndex) - { - if (pageIndex == pageEndIndex) - { - mask &= endMask; - } - - ref ulong pageRef = ref _pageBitmap[pageIndex++]; - - ulong pte = Volatile.Read(ref pageRef); - ulong mappedMask = mask & BlockMappedMask; - - ulong mappedPte = pte | (pte >> 1); - if ((mappedPte & mappedMask) != mappedMask) - { - ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}"); - } - - pte &= mask; - if ((pte & anyTrackingTag) != 0) // Search for any tracking. - { - // Writes trigger any tracking. - // Only trigger tracking from reads if both bits are set on any page. - if (write || (pte & (pte >> 1) & BlockMappedMask) != 0) - { - Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId); - break; - } - } - - mask = ulong.MaxValue; - } - } + _pages.SignalMemoryTracking(Tracking, va, size, write, exemptId); } /// @@ -656,103 +505,28 @@ namespace Ryujinx.Cpu.AppleHv } /// - public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection) + public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest) { - // Protection is inverted on software pages, since the default value is 0. - protection = (~protection) & MemoryPermission.ReadAndWrite; - - int pages = GetPagesCount(va, size, out va); - ulong pageStart = va >> PageBits; - - if (pages == 1) + if (guest) { - ulong protTag = protection switch - { - MemoryPermission.None => (ulong)HostMappedPtBits.Mapped, - MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTracked, - _ => (ulong)HostMappedPtBits.ReadWriteTracked, - }; - - int bit = (int)((pageStart & 31) << 1); - - ulong tagMask = 3UL << bit; - ulong invTagMask = ~tagMask; - - ulong tag = protTag << bit; - - int pageIndex = (int)(pageStart >> PageToPteShift); - ref ulong pageRef = ref _pageBitmap[pageIndex]; - - ulong pte; - - do - { - pte = Volatile.Read(ref pageRef); - } - while ((pte & tagMask) != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte); + _addressSpace.ReprotectUser(va, size, protection); } else { - ulong pageEnd = pageStart + (ulong)pages; - - GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); - - ulong mask = startMask; - - ulong protTag = protection switch - { - MemoryPermission.None => (ulong)HostMappedPtBits.MappedReplicated, - MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTrackedReplicated, - _ => (ulong)HostMappedPtBits.ReadWriteTrackedReplicated, - }; - - while (pageIndex <= pageEndIndex) - { - if (pageIndex == pageEndIndex) - { - mask &= endMask; - } - - ref ulong pageRef = ref _pageBitmap[pageIndex++]; - - ulong pte; - ulong mappedMask; - - // Change the protection of all 2 bit entries that are mapped. - do - { - pte = Volatile.Read(ref pageRef); - - mappedMask = pte | (pte >> 1); - mappedMask |= (mappedMask & BlockMappedMask) << 1; - mappedMask &= mask; // Only update mapped pages within the given range. - } - while (Interlocked.CompareExchange(ref pageRef, (pte & (~mappedMask)) | (protTag & mappedMask), pte) != pte); - - mask = ulong.MaxValue; - } + _pages.TrackingReprotect(va, size, protection); } - - protection = protection switch - { - MemoryPermission.None => MemoryPermission.ReadAndWrite, - MemoryPermission.Write => MemoryPermission.Read, - _ => MemoryPermission.None, - }; - - _addressSpace.ReprotectUser(va, size, protection); } /// - public RegionHandle BeginTracking(ulong address, ulong size, int id) + public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None) { - return Tracking.BeginTracking(address, size, id); + return Tracking.BeginTracking(address, size, id, flags); } /// - public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id) + public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None) { - return Tracking.BeginGranularTracking(address, size, handles, granularity, id); + return Tracking.BeginGranularTracking(address, size, handles, granularity, id, flags); } /// @@ -761,86 +535,6 @@ namespace Ryujinx.Cpu.AppleHv return Tracking.BeginSmartGranularTracking(address, size, granularity, id); } - /// - /// Adds the given address mapping to the page table. - /// - /// Virtual memory address - /// Size to be mapped - private void AddMapping(ulong va, ulong size) - { - int pages = GetPagesCount(va, size, out _); - ulong pageStart = va >> PageBits; - ulong pageEnd = pageStart + (ulong)pages; - - GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); - - ulong mask = startMask; - - while (pageIndex <= pageEndIndex) - { - if (pageIndex == pageEndIndex) - { - mask &= endMask; - } - - ref ulong pageRef = ref _pageBitmap[pageIndex++]; - - ulong pte; - ulong mappedMask; - - // Map all 2-bit entries that are unmapped. - do - { - pte = Volatile.Read(ref pageRef); - - mappedMask = pte | (pte >> 1); - mappedMask |= (mappedMask & BlockMappedMask) << 1; - mappedMask |= ~mask; // Treat everything outside the range as mapped, thus unchanged. - } - while (Interlocked.CompareExchange(ref pageRef, (pte & mappedMask) | (BlockMappedMask & (~mappedMask)), pte) != pte); - - mask = ulong.MaxValue; - } - } - - /// - /// Removes the given address mapping from the page table. - /// - /// Virtual memory address - /// Size to be unmapped - private void RemoveMapping(ulong va, ulong size) - { - int pages = GetPagesCount(va, size, out _); - ulong pageStart = va >> PageBits; - ulong pageEnd = pageStart + (ulong)pages; - - GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); - - startMask = ~startMask; - endMask = ~endMask; - - ulong mask = startMask; - - while (pageIndex <= pageEndIndex) - { - if (pageIndex == pageEndIndex) - { - mask |= endMask; - } - - ref ulong pageRef = ref _pageBitmap[pageIndex++]; - ulong pte; - - do - { - pte = Volatile.Read(ref pageRef); - } - while (Interlocked.CompareExchange(ref pageRef, pte & mask, pte) != pte); - - mask = 0; - } - } - private ulong GetPhysicalAddressChecked(ulong va) { if (!IsMapped(va)) diff --git a/src/Ryujinx.Cpu/IVirtualMemoryManagerTracked.cs b/src/Ryujinx.Cpu/IVirtualMemoryManagerTracked.cs index 199bff240e..e8d11ede5e 100644 --- a/src/Ryujinx.Cpu/IVirtualMemoryManagerTracked.cs +++ b/src/Ryujinx.Cpu/IVirtualMemoryManagerTracked.cs @@ -28,8 +28,9 @@ namespace Ryujinx.Cpu /// CPU virtual address of the region /// Size of the region /// Handle ID + /// Region flags /// The memory tracking handle - RegionHandle BeginTracking(ulong address, ulong size, int id); + RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None); /// /// Obtains a memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with. @@ -39,8 +40,9 @@ namespace Ryujinx.Cpu /// Handles to inherit state from or reuse. When none are present, provide null /// Desired granularity of write tracking /// Handle ID + /// Region flags /// The memory tracking handle - MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id); + MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None); /// /// Obtains a smart memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with. diff --git a/src/Ryujinx.Cpu/Jit/MemoryManager.cs b/src/Ryujinx.Cpu/Jit/MemoryManager.cs index bbfdf536eb..c87c8b8cc5 100644 --- a/src/Ryujinx.Cpu/Jit/MemoryManager.cs +++ b/src/Ryujinx.Cpu/Jit/MemoryManager.cs @@ -33,6 +33,8 @@ namespace Ryujinx.Cpu.Jit private readonly MemoryBlock _pageTable; + private readonly ManagedPageFlags _pages; + /// /// Page table base pointer. /// @@ -70,6 +72,8 @@ namespace Ryujinx.Cpu.Jit AddressSpaceSize = asSize; _pageTable = new MemoryBlock((asSize / PageSize) * PteSize); + _pages = new ManagedPageFlags(AddressSpaceBits); + Tracking = new MemoryTracking(this, PageSize); } @@ -89,6 +93,7 @@ namespace Ryujinx.Cpu.Jit remainingSize -= PageSize; } + _pages.AddMapping(oVa, size); Tracking.Map(oVa, size); } @@ -111,6 +116,7 @@ namespace Ryujinx.Cpu.Jit UnmapEvent?.Invoke(va, size); Tracking.Unmap(va, size); + _pages.RemoveMapping(va, size); ulong remainingSize = size; while (remainingSize != 0) @@ -148,6 +154,26 @@ namespace Ryujinx.Cpu.Jit } } + /// + public T ReadGuest(ulong va) where T : unmanaged + { + try + { + SignalMemoryTrackingImpl(va, (ulong)Unsafe.SizeOf(), false, true); + + return Read(va); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + + return default; + } + } + /// public override void Read(ulong va, Span data) { @@ -183,6 +209,16 @@ namespace Ryujinx.Cpu.Jit WriteImpl(va, data); } + /// + public void WriteGuest(ulong va, T value) where T : unmanaged + { + Span data = MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1)); + + SignalMemoryTrackingImpl(va, (ulong)data.Length, true, true); + + WriteImpl(va, data); + } + /// public void WriteUntracked(ulong va, ReadOnlySpan data) { @@ -520,50 +556,57 @@ namespace Ryujinx.Cpu.Jit } /// - public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection) + public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest) { AssertValidAddressAndSize(va, size); - // Protection is inverted on software pages, since the default value is 0. - protection = (~protection) & MemoryPermission.ReadAndWrite; - - long tag = protection switch + if (guest) { - MemoryPermission.None => 0L, - MemoryPermission.Write => 2L << PointerTagBit, - _ => 3L << PointerTagBit, - }; + // Protection is inverted on software pages, since the default value is 0. + protection = (~protection) & MemoryPermission.ReadAndWrite; - int pages = GetPagesCount(va, (uint)size, out va); - ulong pageStart = va >> PageBits; - long invTagMask = ~(0xffffL << 48); - - for (int page = 0; page < pages; page++) - { - ref long pageRef = ref _pageTable.GetRef(pageStart * PteSize); - - long pte; - - do + long tag = protection switch { - pte = Volatile.Read(ref pageRef); - } - while (pte != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte); + MemoryPermission.None => 0L, + MemoryPermission.Write => 2L << PointerTagBit, + _ => 3L << PointerTagBit, + }; - pageStart++; + int pages = GetPagesCount(va, (uint)size, out va); + ulong pageStart = va >> PageBits; + long invTagMask = ~(0xffffL << 48); + + for (int page = 0; page < pages; page++) + { + ref long pageRef = ref _pageTable.GetRef(pageStart * PteSize); + + long pte; + + do + { + pte = Volatile.Read(ref pageRef); + } + while (pte != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte); + + pageStart++; + } + } + else + { + _pages.TrackingReprotect(va, size, protection); } } /// - public RegionHandle BeginTracking(ulong address, ulong size, int id) + public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None) { - return Tracking.BeginTracking(address, size, id); + return Tracking.BeginTracking(address, size, id, flags); } /// - public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id) + public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None) { - return Tracking.BeginGranularTracking(address, size, handles, granularity, id); + return Tracking.BeginGranularTracking(address, size, handles, granularity, id, flags); } /// @@ -572,8 +615,7 @@ namespace Ryujinx.Cpu.Jit return Tracking.BeginSmartGranularTracking(address, size, granularity, id); } - /// - public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) + private void SignalMemoryTrackingImpl(ulong va, ulong size, bool write, bool guest, bool precise = false, int? exemptId = null) { AssertValidAddressAndSize(va, size); @@ -583,31 +625,47 @@ namespace Ryujinx.Cpu.Jit return; } - // We emulate guard pages for software memory access. This makes for an easy transition to - // tracking using host guard pages in future, but also supporting platforms where this is not possible. + // If the memory tracking is coming from the guest, use the tag bits in the page table entry. + // Otherwise, use the managed page flags. - // Write tag includes read protection, since we don't have any read actions that aren't performed before write too. - long tag = (write ? 3L : 1L) << PointerTagBit; - - int pages = GetPagesCount(va, (uint)size, out _); - ulong pageStart = va >> PageBits; - - for (int page = 0; page < pages; page++) + if (guest) { - ref long pageRef = ref _pageTable.GetRef(pageStart * PteSize); + // We emulate guard pages for software memory access. This makes for an easy transition to + // tracking using host guard pages in future, but also supporting platforms where this is not possible. - long pte; + // Write tag includes read protection, since we don't have any read actions that aren't performed before write too. + long tag = (write ? 3L : 1L) << PointerTagBit; - pte = Volatile.Read(ref pageRef); + int pages = GetPagesCount(va, (uint)size, out _); + ulong pageStart = va >> PageBits; - if ((pte & tag) != 0) + for (int page = 0; page < pages; page++) { - Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId); - break; - } + ref long pageRef = ref _pageTable.GetRef(pageStart * PteSize); - pageStart++; + long pte; + + pte = Volatile.Read(ref pageRef); + + if ((pte & tag) != 0) + { + Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId, true); + break; + } + + pageStart++; + } } + else + { + _pages.SignalMemoryTracking(Tracking, va, size, write, exemptId); + } + } + + /// + public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) + { + SignalMemoryTrackingImpl(va, size, write, false, precise, exemptId); } private ulong PaToPte(ulong pa) diff --git a/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs b/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs index 0b6ba260ab..f410d02e96 100644 --- a/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs +++ b/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; -using System.Threading; namespace Ryujinx.Cpu.Jit { @@ -15,21 +14,6 @@ namespace Ryujinx.Cpu.Jit /// public sealed class MemoryManagerHostMapped : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock { - public const int PageToPteShift = 5; // 32 pages (2 bits each) in one ulong page table entry. - public const ulong BlockMappedMask = 0x5555555555555555; // First bit of each table entry set. - - private enum HostMappedPtBits : ulong - { - Unmapped = 0, - Mapped, - WriteTracked, - ReadWriteTracked, - - MappedReplicated = 0x5555555555555555, - WriteTrackedReplicated = 0xaaaaaaaaaaaaaaaa, - ReadWriteTrackedReplicated = ulong.MaxValue, - } - private readonly InvalidAccessHandler _invalidAccessHandler; private readonly bool _unsafeMode; @@ -39,7 +23,7 @@ namespace Ryujinx.Cpu.Jit private readonly MemoryEhMeilleure _memoryEh; - private readonly ulong[] _pageBitmap; + private readonly ManagedPageFlags _pages; /// public bool Supports4KBPages => MemoryBlock.GetPageSize() == PageSize; @@ -81,7 +65,7 @@ namespace Ryujinx.Cpu.Jit AddressSpaceBits = asBits; - _pageBitmap = new ulong[1 << (AddressSpaceBits - (PageBits + PageToPteShift))]; + _pages = new ManagedPageFlags(AddressSpaceBits); Tracking = new MemoryTracking(this, (int)MemoryBlock.GetPageSize(), invalidAccessHandler); _memoryEh = new MemoryEhMeilleure(_addressSpace.Base, _addressSpace.Mirror, Tracking); @@ -94,7 +78,7 @@ namespace Ryujinx.Cpu.Jit /// Size of the range in bytes private void AssertMapped(ulong va, ulong size) { - if (!ValidateAddressAndSize(va, size) || !IsRangeMappedImpl(va, size)) + if (!ValidateAddressAndSize(va, size) || !_pages.IsRangeMapped(va, size)) { throw new InvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}"); } @@ -106,7 +90,7 @@ namespace Ryujinx.Cpu.Jit AssertValidAddressAndSize(va, size); _addressSpace.Map(va, pa, size, flags); - AddMapping(va, size); + _pages.AddMapping(va, size); PtMap(va, pa, size); Tracking.Map(va, size); @@ -126,7 +110,7 @@ namespace Ryujinx.Cpu.Jit UnmapEvent?.Invoke(va, size); Tracking.Unmap(va, size); - RemoveMapping(va, size); + _pages.RemoveMapping(va, size); PtUnmap(va, size); _addressSpace.Unmap(va, size); } @@ -337,22 +321,7 @@ namespace Ryujinx.Cpu.Jit [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsMapped(ulong va) { - return ValidateAddress(va) && IsMappedImpl(va); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool IsMappedImpl(ulong va) - { - ulong page = va >> PageBits; - - int bit = (int)((page & 31) << 1); - - int pageIndex = (int)(page >> PageToPteShift); - ref ulong pageRef = ref _pageBitmap[pageIndex]; - - ulong pte = Volatile.Read(ref pageRef); - - return ((pte >> bit) & 3) != 0; + return ValidateAddress(va) && _pages.IsMapped(va); } /// @@ -360,58 +329,7 @@ namespace Ryujinx.Cpu.Jit { AssertValidAddressAndSize(va, size); - return IsRangeMappedImpl(va, size); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void GetPageBlockRange(ulong pageStart, ulong pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex) - { - startMask = ulong.MaxValue << ((int)(pageStart & 31) << 1); - endMask = ulong.MaxValue >> (64 - ((int)(pageEnd & 31) << 1)); - - pageIndex = (int)(pageStart >> PageToPteShift); - pageEndIndex = (int)((pageEnd - 1) >> PageToPteShift); - } - - private bool IsRangeMappedImpl(ulong va, ulong size) - { - int pages = GetPagesCount(va, size, out _); - - if (pages == 1) - { - return IsMappedImpl(va); - } - - ulong pageStart = va >> PageBits; - ulong pageEnd = pageStart + (ulong)pages; - - GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); - - // Check if either bit in each 2 bit page entry is set. - // OR the block with itself shifted down by 1, and check the first bit of each entry. - - ulong mask = BlockMappedMask & startMask; - - while (pageIndex <= pageEndIndex) - { - if (pageIndex == pageEndIndex) - { - mask &= endMask; - } - - ref ulong pageRef = ref _pageBitmap[pageIndex++]; - ulong pte = Volatile.Read(ref pageRef); - - pte |= pte >> 1; - if ((pte & mask) != mask) - { - return false; - } - - mask = BlockMappedMask; - } - - return true; + return _pages.IsRangeMapped(va, size); } /// @@ -486,76 +404,7 @@ namespace Ryujinx.Cpu.Jit return; } - // Software table, used for managed memory tracking. - - int pages = GetPagesCount(va, size, out _); - ulong pageStart = va >> PageBits; - - if (pages == 1) - { - ulong tag = (ulong)(write ? HostMappedPtBits.WriteTracked : HostMappedPtBits.ReadWriteTracked); - - int bit = (int)((pageStart & 31) << 1); - - int pageIndex = (int)(pageStart >> PageToPteShift); - ref ulong pageRef = ref _pageBitmap[pageIndex]; - - ulong pte = Volatile.Read(ref pageRef); - ulong state = ((pte >> bit) & 3); - - if (state >= tag) - { - Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId); - return; - } - else if (state == 0) - { - ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}"); - } - } - else - { - ulong pageEnd = pageStart + (ulong)pages; - - GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); - - ulong mask = startMask; - - ulong anyTrackingTag = (ulong)HostMappedPtBits.WriteTrackedReplicated; - - while (pageIndex <= pageEndIndex) - { - if (pageIndex == pageEndIndex) - { - mask &= endMask; - } - - ref ulong pageRef = ref _pageBitmap[pageIndex++]; - - ulong pte = Volatile.Read(ref pageRef); - ulong mappedMask = mask & BlockMappedMask; - - ulong mappedPte = pte | (pte >> 1); - if ((mappedPte & mappedMask) != mappedMask) - { - ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}"); - } - - pte &= mask; - if ((pte & anyTrackingTag) != 0) // Search for any tracking. - { - // Writes trigger any tracking. - // Only trigger tracking from reads if both bits are set on any page. - if (write || (pte & (pte >> 1) & BlockMappedMask) != 0) - { - Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId); - break; - } - } - - mask = ulong.MaxValue; - } - } + _pages.SignalMemoryTracking(Tracking, va, size, write, exemptId); } /// @@ -582,103 +431,28 @@ namespace Ryujinx.Cpu.Jit } /// - public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection) + public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest) { - // Protection is inverted on software pages, since the default value is 0. - protection = (~protection) & MemoryPermission.ReadAndWrite; - - int pages = GetPagesCount(va, size, out va); - ulong pageStart = va >> PageBits; - - if (pages == 1) + if (guest) { - ulong protTag = protection switch - { - MemoryPermission.None => (ulong)HostMappedPtBits.Mapped, - MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTracked, - _ => (ulong)HostMappedPtBits.ReadWriteTracked, - }; - - int bit = (int)((pageStart & 31) << 1); - - ulong tagMask = 3UL << bit; - ulong invTagMask = ~tagMask; - - ulong tag = protTag << bit; - - int pageIndex = (int)(pageStart >> PageToPteShift); - ref ulong pageRef = ref _pageBitmap[pageIndex]; - - ulong pte; - - do - { - pte = Volatile.Read(ref pageRef); - } - while ((pte & tagMask) != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte); + _addressSpace.Base.Reprotect(va, size, protection, false); } else { - ulong pageEnd = pageStart + (ulong)pages; - - GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); - - ulong mask = startMask; - - ulong protTag = protection switch - { - MemoryPermission.None => (ulong)HostMappedPtBits.MappedReplicated, - MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTrackedReplicated, - _ => (ulong)HostMappedPtBits.ReadWriteTrackedReplicated, - }; - - while (pageIndex <= pageEndIndex) - { - if (pageIndex == pageEndIndex) - { - mask &= endMask; - } - - ref ulong pageRef = ref _pageBitmap[pageIndex++]; - - ulong pte; - ulong mappedMask; - - // Change the protection of all 2 bit entries that are mapped. - do - { - pte = Volatile.Read(ref pageRef); - - mappedMask = pte | (pte >> 1); - mappedMask |= (mappedMask & BlockMappedMask) << 1; - mappedMask &= mask; // Only update mapped pages within the given range. - } - while (Interlocked.CompareExchange(ref pageRef, (pte & (~mappedMask)) | (protTag & mappedMask), pte) != pte); - - mask = ulong.MaxValue; - } + _pages.TrackingReprotect(va, size, protection); } - - protection = protection switch - { - MemoryPermission.None => MemoryPermission.ReadAndWrite, - MemoryPermission.Write => MemoryPermission.Read, - _ => MemoryPermission.None, - }; - - _addressSpace.Base.Reprotect(va, size, protection, false); } /// - public RegionHandle BeginTracking(ulong address, ulong size, int id) + public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None) { - return Tracking.BeginTracking(address, size, id); + return Tracking.BeginTracking(address, size, id, flags); } /// - public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id) + public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None) { - return Tracking.BeginGranularTracking(address, size, handles, granularity, id); + return Tracking.BeginGranularTracking(address, size, handles, granularity, id, flags); } /// @@ -687,86 +461,6 @@ namespace Ryujinx.Cpu.Jit return Tracking.BeginSmartGranularTracking(address, size, granularity, id); } - /// - /// Adds the given address mapping to the page table. - /// - /// Virtual memory address - /// Size to be mapped - private void AddMapping(ulong va, ulong size) - { - int pages = GetPagesCount(va, size, out _); - ulong pageStart = va >> PageBits; - ulong pageEnd = pageStart + (ulong)pages; - - GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); - - ulong mask = startMask; - - while (pageIndex <= pageEndIndex) - { - if (pageIndex == pageEndIndex) - { - mask &= endMask; - } - - ref ulong pageRef = ref _pageBitmap[pageIndex++]; - - ulong pte; - ulong mappedMask; - - // Map all 2-bit entries that are unmapped. - do - { - pte = Volatile.Read(ref pageRef); - - mappedMask = pte | (pte >> 1); - mappedMask |= (mappedMask & BlockMappedMask) << 1; - mappedMask |= ~mask; // Treat everything outside the range as mapped, thus unchanged. - } - while (Interlocked.CompareExchange(ref pageRef, (pte & mappedMask) | (BlockMappedMask & (~mappedMask)), pte) != pte); - - mask = ulong.MaxValue; - } - } - - /// - /// Removes the given address mapping from the page table. - /// - /// Virtual memory address - /// Size to be unmapped - private void RemoveMapping(ulong va, ulong size) - { - int pages = GetPagesCount(va, size, out _); - ulong pageStart = va >> PageBits; - ulong pageEnd = pageStart + (ulong)pages; - - GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); - - startMask = ~startMask; - endMask = ~endMask; - - ulong mask = startMask; - - while (pageIndex <= pageEndIndex) - { - if (pageIndex == pageEndIndex) - { - mask |= endMask; - } - - ref ulong pageRef = ref _pageBitmap[pageIndex++]; - ulong pte; - - do - { - pte = Volatile.Read(ref pageRef); - } - while (Interlocked.CompareExchange(ref pageRef, pte & mask, pte) != pte); - - mask = 0; - } - } - /// /// Disposes of resources used by the memory manager. /// diff --git a/src/Ryujinx.Cpu/ManagedPageFlags.cs b/src/Ryujinx.Cpu/ManagedPageFlags.cs new file mode 100644 index 0000000000..a839dae676 --- /dev/null +++ b/src/Ryujinx.Cpu/ManagedPageFlags.cs @@ -0,0 +1,389 @@ +using Ryujinx.Memory; +using Ryujinx.Memory.Tracking; +using System; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace Ryujinx.Cpu +{ + /// + /// A page bitmap that keeps track of mapped state and tracking protection + /// for managed memory accesses (not using host page protection). + /// + internal readonly struct ManagedPageFlags + { + public const int PageBits = 12; + public const int PageSize = 1 << PageBits; + public const int PageMask = PageSize - 1; + + private readonly ulong[] _pageBitmap; + + public const int PageToPteShift = 5; // 32 pages (2 bits each) in one ulong page table entry. + public const ulong BlockMappedMask = 0x5555555555555555; // First bit of each table entry set. + + private enum ManagedPtBits : ulong + { + Unmapped = 0, + Mapped, + WriteTracked, + ReadWriteTracked, + + MappedReplicated = 0x5555555555555555, + WriteTrackedReplicated = 0xaaaaaaaaaaaaaaaa, + ReadWriteTrackedReplicated = ulong.MaxValue, + } + + public ManagedPageFlags(int addressSpaceBits) + { + int bits = Math.Max(0, addressSpaceBits - (PageBits + PageToPteShift)); + _pageBitmap = new ulong[1 << bits]; + } + + /// + /// Computes the number of pages in a virtual address range. + /// + /// Virtual address of the range + /// Size of the range + /// The virtual address of the beginning of the first page + /// This function does not differentiate between allocated and unallocated pages. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetPagesCount(ulong va, ulong size, out ulong startVa) + { + // WARNING: Always check if ulong does not overflow during the operations. + startVa = va & ~(ulong)PageMask; + ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask; + + return (int)(vaSpan / PageSize); + } + + /// + /// Checks if the page at a given CPU virtual address is mapped. + /// + /// Virtual address to check + /// True if the address is mapped, false otherwise + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool IsMapped(ulong va) + { + ulong page = va >> PageBits; + + int bit = (int)((page & 31) << 1); + + int pageIndex = (int)(page >> PageToPteShift); + ref ulong pageRef = ref _pageBitmap[pageIndex]; + + ulong pte = Volatile.Read(ref pageRef); + + return ((pte >> bit) & 3) != 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void GetPageBlockRange(ulong pageStart, ulong pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex) + { + startMask = ulong.MaxValue << ((int)(pageStart & 31) << 1); + endMask = ulong.MaxValue >> (64 - ((int)(pageEnd & 31) << 1)); + + pageIndex = (int)(pageStart >> PageToPteShift); + pageEndIndex = (int)((pageEnd - 1) >> PageToPteShift); + } + + /// + /// Checks if a memory range is mapped. + /// + /// Virtual address of the range + /// Size of the range in bytes + /// True if the entire range is mapped, false otherwise + public readonly bool IsRangeMapped(ulong va, ulong size) + { + int pages = GetPagesCount(va, size, out _); + + if (pages == 1) + { + return IsMapped(va); + } + + ulong pageStart = va >> PageBits; + ulong pageEnd = pageStart + (ulong)pages; + + GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); + + // Check if either bit in each 2 bit page entry is set. + // OR the block with itself shifted down by 1, and check the first bit of each entry. + + ulong mask = BlockMappedMask & startMask; + + while (pageIndex <= pageEndIndex) + { + if (pageIndex == pageEndIndex) + { + mask &= endMask; + } + + ref ulong pageRef = ref _pageBitmap[pageIndex++]; + ulong pte = Volatile.Read(ref pageRef); + + pte |= pte >> 1; + if ((pte & mask) != mask) + { + return false; + } + + mask = BlockMappedMask; + } + + return true; + } + + /// + /// Reprotect a region of virtual memory for tracking. + /// + /// Virtual address base + /// Size of the region to protect + /// Memory protection to set + public readonly void TrackingReprotect(ulong va, ulong size, MemoryPermission protection) + { + // Protection is inverted on software pages, since the default value is 0. + protection = (~protection) & MemoryPermission.ReadAndWrite; + + int pages = GetPagesCount(va, size, out va); + ulong pageStart = va >> PageBits; + + if (pages == 1) + { + ulong protTag = protection switch + { + MemoryPermission.None => (ulong)ManagedPtBits.Mapped, + MemoryPermission.Write => (ulong)ManagedPtBits.WriteTracked, + _ => (ulong)ManagedPtBits.ReadWriteTracked, + }; + + int bit = (int)((pageStart & 31) << 1); + + ulong tagMask = 3UL << bit; + ulong invTagMask = ~tagMask; + + ulong tag = protTag << bit; + + int pageIndex = (int)(pageStart >> PageToPteShift); + ref ulong pageRef = ref _pageBitmap[pageIndex]; + + ulong pte; + + do + { + pte = Volatile.Read(ref pageRef); + } + while ((pte & tagMask) != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte); + } + else + { + ulong pageEnd = pageStart + (ulong)pages; + + GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); + + ulong mask = startMask; + + ulong protTag = protection switch + { + MemoryPermission.None => (ulong)ManagedPtBits.MappedReplicated, + MemoryPermission.Write => (ulong)ManagedPtBits.WriteTrackedReplicated, + _ => (ulong)ManagedPtBits.ReadWriteTrackedReplicated, + }; + + while (pageIndex <= pageEndIndex) + { + if (pageIndex == pageEndIndex) + { + mask &= endMask; + } + + ref ulong pageRef = ref _pageBitmap[pageIndex++]; + + ulong pte; + ulong mappedMask; + + // Change the protection of all 2 bit entries that are mapped. + do + { + pte = Volatile.Read(ref pageRef); + + mappedMask = pte | (pte >> 1); + mappedMask |= (mappedMask & BlockMappedMask) << 1; + mappedMask &= mask; // Only update mapped pages within the given range. + } + while (Interlocked.CompareExchange(ref pageRef, (pte & (~mappedMask)) | (protTag & mappedMask), pte) != pte); + + mask = ulong.MaxValue; + } + } + } + + /// + /// Alerts the memory tracking that a given region has been read from or written to. + /// This should be called before read/write is performed. + /// + /// Memory tracking structure to call when pages are protected + /// Virtual address of the region + /// Size of the region + /// True if the region was written, false if read + /// Optional ID of the handles that should not be signalled + /// + /// This function also validates that the given range is both valid and mapped, and will throw if it is not. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly void SignalMemoryTracking(MemoryTracking tracking, ulong va, ulong size, bool write, int? exemptId = null) + { + // Software table, used for managed memory tracking. + + int pages = GetPagesCount(va, size, out _); + ulong pageStart = va >> PageBits; + + if (pages == 1) + { + ulong tag = (ulong)(write ? ManagedPtBits.WriteTracked : ManagedPtBits.ReadWriteTracked); + + int bit = (int)((pageStart & 31) << 1); + + int pageIndex = (int)(pageStart >> PageToPteShift); + ref ulong pageRef = ref _pageBitmap[pageIndex]; + + ulong pte = Volatile.Read(ref pageRef); + ulong state = ((pte >> bit) & 3); + + if (state >= tag) + { + tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId); + return; + } + else if (state == 0) + { + ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}"); + } + } + else + { + ulong pageEnd = pageStart + (ulong)pages; + + GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); + + ulong mask = startMask; + + ulong anyTrackingTag = (ulong)ManagedPtBits.WriteTrackedReplicated; + + while (pageIndex <= pageEndIndex) + { + if (pageIndex == pageEndIndex) + { + mask &= endMask; + } + + ref ulong pageRef = ref _pageBitmap[pageIndex++]; + + ulong pte = Volatile.Read(ref pageRef); + ulong mappedMask = mask & BlockMappedMask; + + ulong mappedPte = pte | (pte >> 1); + if ((mappedPte & mappedMask) != mappedMask) + { + ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}"); + } + + pte &= mask; + if ((pte & anyTrackingTag) != 0) // Search for any tracking. + { + // Writes trigger any tracking. + // Only trigger tracking from reads if both bits are set on any page. + if (write || (pte & (pte >> 1) & BlockMappedMask) != 0) + { + tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId); + break; + } + } + + mask = ulong.MaxValue; + } + } + } + + /// + /// Adds the given address mapping to the page table. + /// + /// Virtual memory address + /// Size to be mapped + public readonly void AddMapping(ulong va, ulong size) + { + int pages = GetPagesCount(va, size, out _); + ulong pageStart = va >> PageBits; + ulong pageEnd = pageStart + (ulong)pages; + + GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); + + ulong mask = startMask; + + while (pageIndex <= pageEndIndex) + { + if (pageIndex == pageEndIndex) + { + mask &= endMask; + } + + ref ulong pageRef = ref _pageBitmap[pageIndex++]; + + ulong pte; + ulong mappedMask; + + // Map all 2-bit entries that are unmapped. + do + { + pte = Volatile.Read(ref pageRef); + + mappedMask = pte | (pte >> 1); + mappedMask |= (mappedMask & BlockMappedMask) << 1; + mappedMask |= ~mask; // Treat everything outside the range as mapped, thus unchanged. + } + while (Interlocked.CompareExchange(ref pageRef, (pte & mappedMask) | (BlockMappedMask & (~mappedMask)), pte) != pte); + + mask = ulong.MaxValue; + } + } + + /// + /// Removes the given address mapping from the page table. + /// + /// Virtual memory address + /// Size to be unmapped + public readonly void RemoveMapping(ulong va, ulong size) + { + int pages = GetPagesCount(va, size, out _); + ulong pageStart = va >> PageBits; + ulong pageEnd = pageStart + (ulong)pages; + + GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); + + startMask = ~startMask; + endMask = ~endMask; + + ulong mask = startMask; + + while (pageIndex <= pageEndIndex) + { + if (pageIndex == pageEndIndex) + { + mask |= endMask; + } + + ref ulong pageRef = ref _pageBitmap[pageIndex++]; + ulong pte; + + do + { + pte = Volatile.Read(ref pageRef); + } + while (Interlocked.CompareExchange(ref pageRef, pte & mask, pte) != pte); + + mask = 0; + } + } + + private static void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message); + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/Pool.cs b/src/Ryujinx.Graphics.Gpu/Image/Pool.cs index 0c3a219de0..6ede019710 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/Pool.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/Pool.cs @@ -69,7 +69,7 @@ namespace Ryujinx.Graphics.Gpu.Image Address = address; Size = size; - _memoryTracking = physicalMemory.BeginGranularTracking(address, size, ResourceKind.Pool); + _memoryTracking = physicalMemory.BeginGranularTracking(address, size, ResourceKind.Pool, RegionFlags.None); _memoryTracking.RegisterPreciseAction(address, size, PreciseAction); _modifiedDelegate = RegionModified; } diff --git a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs index e01e5142c8..d293060b5a 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs @@ -128,13 +128,13 @@ namespace Ryujinx.Graphics.Gpu.Memory if (_useGranular) { - _memoryTrackingGranular = physicalMemory.BeginGranularTracking(address, size, ResourceKind.Buffer, baseHandles); + _memoryTrackingGranular = physicalMemory.BeginGranularTracking(address, size, ResourceKind.Buffer, RegionFlags.UnalignedAccess, baseHandles); _memoryTrackingGranular.RegisterPreciseAction(address, size, PreciseAction); } else { - _memoryTracking = physicalMemory.BeginTracking(address, size, ResourceKind.Buffer); + _memoryTracking = physicalMemory.BeginTracking(address, size, ResourceKind.Buffer, RegionFlags.UnalignedAccess); if (baseHandles != null) { diff --git a/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs b/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs index 69a3054a49..cca02bb156 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs @@ -368,10 +368,11 @@ namespace Ryujinx.Graphics.Gpu.Memory /// CPU virtual address of the region /// Size of the region /// Kind of the resource being tracked + /// Region flags /// The memory tracking handle - public RegionHandle BeginTracking(ulong address, ulong size, ResourceKind kind) + public RegionHandle BeginTracking(ulong address, ulong size, ResourceKind kind, RegionFlags flags = RegionFlags.None) { - return _cpuMemory.BeginTracking(address, size, (int)kind); + return _cpuMemory.BeginTracking(address, size, (int)kind, flags); } /// @@ -408,12 +409,19 @@ namespace Ryujinx.Graphics.Gpu.Memory /// CPU virtual address of the region /// Size of the region /// Kind of the resource being tracked + /// Region flags /// Handles to inherit state from or reuse /// Desired granularity of write tracking /// The memory tracking handle - public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, ResourceKind kind, IEnumerable handles = null, ulong granularity = 4096) + public MultiRegionHandle BeginGranularTracking( + ulong address, + ulong size, + ResourceKind kind, + RegionFlags flags = RegionFlags.None, + IEnumerable handles = null, + ulong granularity = 4096) { - return _cpuMemory.BeginGranularTracking(address, size, handles, granularity, (int)kind); + return _cpuMemory.BeginGranularTracking(address, size, handles, granularity, (int)kind, flags); } /// diff --git a/src/Ryujinx.Memory/AddressSpaceManager.cs b/src/Ryujinx.Memory/AddressSpaceManager.cs index b953eb306e..0a4a951439 100644 --- a/src/Ryujinx.Memory/AddressSpaceManager.cs +++ b/src/Ryujinx.Memory/AddressSpaceManager.cs @@ -392,7 +392,7 @@ namespace Ryujinx.Memory } /// - public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection) + public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest = false) { throw new NotImplementedException(); } diff --git a/src/Ryujinx.Memory/IVirtualMemoryManager.cs b/src/Ryujinx.Memory/IVirtualMemoryManager.cs index 9cf3663cfc..557da2f261 100644 --- a/src/Ryujinx.Memory/IVirtualMemoryManager.cs +++ b/src/Ryujinx.Memory/IVirtualMemoryManager.cs @@ -214,6 +214,7 @@ namespace Ryujinx.Memory /// Virtual address base /// Size of the region to protect /// Memory protection to set - void TrackingReprotect(ulong va, ulong size, MemoryPermission protection); + /// True if the protection is for guest access, false otherwise + void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest); } } diff --git a/src/Ryujinx.Memory/Tracking/MemoryTracking.cs b/src/Ryujinx.Memory/Tracking/MemoryTracking.cs index 6febcbbb3f..96cb2c5f52 100644 --- a/src/Ryujinx.Memory/Tracking/MemoryTracking.cs +++ b/src/Ryujinx.Memory/Tracking/MemoryTracking.cs @@ -14,9 +14,14 @@ namespace Ryujinx.Memory.Tracking // Only use these from within the lock. private readonly NonOverlappingRangeList _virtualRegions; + // Guest virtual regions are a subset of the normal virtual regions, with potentially different protection + // and expanded area of effect on platforms that don't support misaligned page protection. + private readonly NonOverlappingRangeList _guestVirtualRegions; private readonly int _pageSize; + private readonly bool _singleByteGuestTracking; + /// /// This lock must be obtained when traversing or updating the region-handle hierarchy. /// It is not required when reading dirty flags. @@ -27,16 +32,27 @@ namespace Ryujinx.Memory.Tracking /// Create a new tracking structure for the given "physical" memory block, /// with a given "virtual" memory manager that will provide mappings and virtual memory protection. /// + /// + /// If is true, the memory manager must also support protection on partially + /// unmapped regions without throwing exceptions or dropping protection on the mapped portion. + /// /// Virtual memory manager - /// Physical memory block /// Page size of the virtual memory space - public MemoryTracking(IVirtualMemoryManager memoryManager, int pageSize, InvalidAccessHandler invalidAccessHandler = null) + /// Method to call for invalid memory accesses + /// True if the guest only signals writes for the first byte + public MemoryTracking( + IVirtualMemoryManager memoryManager, + int pageSize, + InvalidAccessHandler invalidAccessHandler = null, + bool singleByteGuestTracking = false) { _memoryManager = memoryManager; _pageSize = pageSize; _invalidAccessHandler = invalidAccessHandler; + _singleByteGuestTracking = singleByteGuestTracking; _virtualRegions = new NonOverlappingRangeList(); + _guestVirtualRegions = new NonOverlappingRangeList(); } private (ulong address, ulong size) PageAlign(ulong address, ulong size) @@ -62,20 +78,25 @@ namespace Ryujinx.Memory.Tracking { ref var overlaps = ref ThreadStaticArray.Get(); - int count = _virtualRegions.FindOverlapsNonOverlapping(va, size, ref overlaps); - - for (int i = 0; i < count; i++) + for (int type = 0; type < 2; type++) { - VirtualRegion region = overlaps[i]; + NonOverlappingRangeList regions = type == 0 ? _virtualRegions : _guestVirtualRegions; - // If the region has been fully remapped, signal that it has been mapped again. - bool remapped = _memoryManager.IsRangeMapped(region.Address, region.Size); - if (remapped) + int count = regions.FindOverlapsNonOverlapping(va, size, ref overlaps); + + for (int i = 0; i < count; i++) { - region.SignalMappingChanged(true); - } + VirtualRegion region = overlaps[i]; - region.UpdateProtection(); + // If the region has been fully remapped, signal that it has been mapped again. + bool remapped = _memoryManager.IsRangeMapped(region.Address, region.Size); + if (remapped) + { + region.SignalMappingChanged(true); + } + + region.UpdateProtection(); + } } } } @@ -95,27 +116,58 @@ namespace Ryujinx.Memory.Tracking { ref var overlaps = ref ThreadStaticArray.Get(); - int count = _virtualRegions.FindOverlapsNonOverlapping(va, size, ref overlaps); - - for (int i = 0; i < count; i++) + for (int type = 0; type < 2; type++) { - VirtualRegion region = overlaps[i]; + NonOverlappingRangeList regions = type == 0 ? _virtualRegions : _guestVirtualRegions; - region.SignalMappingChanged(false); + int count = regions.FindOverlapsNonOverlapping(va, size, ref overlaps); + + for (int i = 0; i < count; i++) + { + VirtualRegion region = overlaps[i]; + + region.SignalMappingChanged(false); + } } } } + /// + /// Alter a tracked memory region to properly capture unaligned accesses. + /// For most memory manager modes, this does nothing. + /// + /// Original region address + /// Original region size + /// A new address and size for tracking unaligned accesses + internal (ulong newAddress, ulong newSize) GetUnalignedSafeRegion(ulong address, ulong size) + { + if (_singleByteGuestTracking) + { + // The guest only signals the first byte of each memory access with the current memory manager. + // To catch unaligned access properly, we need to also protect the page before the address. + + // Assume that the address and size are already aligned. + + return (address - (ulong)_pageSize, size + (ulong)_pageSize); + } + else + { + return (address, size); + } + } + /// /// Get a list of virtual regions that a handle covers. /// /// Starting virtual memory address of the handle /// Size of the handle's memory region + /// True if getting handles for guest protection, false otherwise /// A list of virtual regions within the given range - internal List GetVirtualRegionsForHandle(ulong va, ulong size) + internal List GetVirtualRegionsForHandle(ulong va, ulong size, bool guest) { List result = new(); - _virtualRegions.GetOrAddRegions(result, va, size, (va, size) => new VirtualRegion(this, va, size)); + NonOverlappingRangeList regions = guest ? _guestVirtualRegions : _virtualRegions; + regions.GetOrAddRegions(result, va, size, (va, size) => new VirtualRegion(this, va, size, guest)); return result; } @@ -126,7 +178,14 @@ namespace Ryujinx.Memory.Tracking /// Region to remove internal void RemoveVirtual(VirtualRegion region) { - _virtualRegions.Remove(region); + if (region.Guest) + { + _guestVirtualRegions.Remove(region); + } + else + { + _virtualRegions.Remove(region); + } } /// @@ -137,10 +196,11 @@ namespace Ryujinx.Memory.Tracking /// Handles to inherit state from or reuse. When none are present, provide null /// Desired granularity of write tracking /// Handle ID + /// Region flags /// The memory tracking handle - public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id) + public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None) { - return new MultiRegionHandle(this, address, size, handles, granularity, id); + return new MultiRegionHandle(this, address, size, handles, granularity, id, flags); } /// @@ -164,15 +224,16 @@ namespace Ryujinx.Memory.Tracking /// CPU virtual address of the region /// Size of the region /// Handle ID + /// Region flags /// The memory tracking handle - public RegionHandle BeginTracking(ulong address, ulong size, int id) + public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None) { var (paAddress, paSize) = PageAlign(address, size); lock (TrackingLock) { bool mapped = _memoryManager.IsRangeMapped(address, size); - RegionHandle handle = new(this, paAddress, paSize, address, size, id, mapped); + RegionHandle handle = new(this, paAddress, paSize, address, size, id, flags, mapped); return handle; } @@ -186,15 +247,16 @@ namespace Ryujinx.Memory.Tracking /// The bitmap owning the dirty flag for this handle /// The bit of this handle within the dirty flag /// Handle ID + /// Region flags /// The memory tracking handle - internal RegionHandle BeginTrackingBitmap(ulong address, ulong size, ConcurrentBitmap bitmap, int bit, int id) + internal RegionHandle BeginTrackingBitmap(ulong address, ulong size, ConcurrentBitmap bitmap, int bit, int id, RegionFlags flags = RegionFlags.None) { var (paAddress, paSize) = PageAlign(address, size); lock (TrackingLock) { bool mapped = _memoryManager.IsRangeMapped(address, size); - RegionHandle handle = new(this, paAddress, paSize, address, size, bitmap, bit, id, mapped); + RegionHandle handle = new(this, paAddress, paSize, address, size, bitmap, bit, id, flags, mapped); return handle; } @@ -202,6 +264,7 @@ namespace Ryujinx.Memory.Tracking /// /// Signal that a virtual memory event happened at the given location. + /// The memory event is assumed to be triggered by guest code. /// /// Virtual address accessed /// Size of the region affected in bytes @@ -209,7 +272,7 @@ namespace Ryujinx.Memory.Tracking /// True if the event triggered any tracking regions, false otherwise public bool VirtualMemoryEvent(ulong address, ulong size, bool write) { - return VirtualMemoryEvent(address, size, write, precise: false, null); + return VirtualMemoryEvent(address, size, write, precise: false, exemptId: null, guest: true); } /// @@ -222,8 +285,9 @@ namespace Ryujinx.Memory.Tracking /// Whether the region was written to or read /// True if the access is precise, false otherwise /// Optional ID that of the handles that should not be signalled + /// True if the access is from the guest, false otherwise /// True if the event triggered any tracking regions, false otherwise - public bool VirtualMemoryEvent(ulong address, ulong size, bool write, bool precise, int? exemptId = null) + public bool VirtualMemoryEvent(ulong address, ulong size, bool write, bool precise, int? exemptId = null, bool guest = false) { // Look up the virtual region using the region list. // Signal up the chain to relevant handles. @@ -234,7 +298,9 @@ namespace Ryujinx.Memory.Tracking { ref var overlaps = ref ThreadStaticArray.Get(); - int count = _virtualRegions.FindOverlapsNonOverlapping(address, size, ref overlaps); + NonOverlappingRangeList regions = guest ? _guestVirtualRegions : _virtualRegions; + + int count = regions.FindOverlapsNonOverlapping(address, size, ref overlaps); if (count == 0 && !precise) { @@ -242,7 +308,7 @@ namespace Ryujinx.Memory.Tracking { // TODO: There is currently the possibility that a page can be protected after its virtual region is removed. // This code handles that case when it happens, but it would be better to find out how this happens. - _memoryManager.TrackingReprotect(address & ~(ulong)(_pageSize - 1), (ulong)_pageSize, MemoryPermission.ReadAndWrite); + _memoryManager.TrackingReprotect(address & ~(ulong)(_pageSize - 1), (ulong)_pageSize, MemoryPermission.ReadAndWrite, guest); return true; // This memory _should_ be mapped, so we need to try again. } else @@ -252,6 +318,12 @@ namespace Ryujinx.Memory.Tracking } else { + if (guest && _singleByteGuestTracking) + { + // Increase the access size to trigger handles with misaligned accesses. + size += (ulong)_pageSize; + } + for (int i = 0; i < count; i++) { VirtualRegion region = overlaps[i]; @@ -285,9 +357,10 @@ namespace Ryujinx.Memory.Tracking /// /// Region to reprotect /// Memory permission to protect with - internal void ProtectVirtualRegion(VirtualRegion region, MemoryPermission permission) + /// True if the protection is for guest access, false otherwise + internal void ProtectVirtualRegion(VirtualRegion region, MemoryPermission permission, bool guest) { - _memoryManager.TrackingReprotect(region.Address, region.Size, permission); + _memoryManager.TrackingReprotect(region.Address, region.Size, permission, guest); } /// diff --git a/src/Ryujinx.Memory/Tracking/MultiRegionHandle.cs b/src/Ryujinx.Memory/Tracking/MultiRegionHandle.cs index 1c1b48b3c9..6fdca69f5f 100644 --- a/src/Ryujinx.Memory/Tracking/MultiRegionHandle.cs +++ b/src/Ryujinx.Memory/Tracking/MultiRegionHandle.cs @@ -37,7 +37,8 @@ namespace Ryujinx.Memory.Tracking ulong size, IEnumerable handles, ulong granularity, - int id) + int id, + RegionFlags flags) { _handles = new RegionHandle[(size + granularity - 1) / granularity]; Granularity = granularity; @@ -62,7 +63,7 @@ namespace Ryujinx.Memory.Tracking // Fill any gap left before this handle. while (i < startIndex) { - RegionHandle fillHandle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i, id); + RegionHandle fillHandle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i, id, flags); fillHandle.Parent = this; _handles[i++] = fillHandle; } @@ -83,7 +84,7 @@ namespace Ryujinx.Memory.Tracking while (i < endIndex) { - RegionHandle splitHandle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i, id); + RegionHandle splitHandle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i, id, flags); splitHandle.Parent = this; splitHandle.Reprotect(handle.Dirty); @@ -106,7 +107,7 @@ namespace Ryujinx.Memory.Tracking // Fill any remaining space with new handles. while (i < _handles.Length) { - RegionHandle handle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i, id); + RegionHandle handle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i, id, flags); handle.Parent = this; _handles[i++] = handle; } diff --git a/src/Ryujinx.Memory/Tracking/RegionFlags.cs b/src/Ryujinx.Memory/Tracking/RegionFlags.cs new file mode 100644 index 0000000000..ceb8e56a63 --- /dev/null +++ b/src/Ryujinx.Memory/Tracking/RegionFlags.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ryujinx.Memory.Tracking +{ + [Flags] + public enum RegionFlags + { + None = 0, + + /// + /// Access to the resource is expected to occasionally be unaligned. + /// With some memory managers, guest protection must extend into the previous page to cover unaligned access. + /// If this is not expected, protection is not altered, which can avoid unintended resource dirty/flush. + /// + UnalignedAccess = 1, + } +} diff --git a/src/Ryujinx.Memory/Tracking/RegionHandle.cs b/src/Ryujinx.Memory/Tracking/RegionHandle.cs index df3d9c311a..a94ffa43c8 100644 --- a/src/Ryujinx.Memory/Tracking/RegionHandle.cs +++ b/src/Ryujinx.Memory/Tracking/RegionHandle.cs @@ -55,6 +55,8 @@ namespace Ryujinx.Memory.Tracking private RegionSignal _preAction; // Action to perform before a read or write. This will block the memory access. private PreciseRegionSignal _preciseAction; // Action to perform on a precise read or write. private readonly List _regions; + private readonly List _guestRegions; + private readonly List _allRegions; private readonly MemoryTracking _tracking; private bool _disposed; @@ -99,6 +101,7 @@ namespace Ryujinx.Memory.Tracking /// The bitmap the dirty flag for this handle is stored in /// The bit index representing the dirty flag for this handle /// Handle ID + /// Region flags /// True if the region handle starts mapped internal RegionHandle( MemoryTracking tracking, @@ -109,6 +112,7 @@ namespace Ryujinx.Memory.Tracking ConcurrentBitmap bitmap, int bit, int id, + RegionFlags flags, bool mapped = true) { Bitmap = bitmap; @@ -128,11 +132,12 @@ namespace Ryujinx.Memory.Tracking RealEndAddress = realAddress + realSize; _tracking = tracking; - _regions = tracking.GetVirtualRegionsForHandle(address, size); - foreach (var region in _regions) - { - region.Handles.Add(this); - } + + _regions = tracking.GetVirtualRegionsForHandle(address, size, false); + _guestRegions = GetGuestRegions(tracking, address, size, flags); + _allRegions = new List(_regions.Count + _guestRegions.Count); + + InitializeRegions(); } /// @@ -145,8 +150,9 @@ namespace Ryujinx.Memory.Tracking /// The real, unaligned address of the handle /// The real, unaligned size of the handle /// Handle ID + /// Region flags /// True if the region handle starts mapped - internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, ulong realAddress, ulong realSize, int id, bool mapped = true) + internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, ulong realAddress, ulong realSize, int id, RegionFlags flags, bool mapped = true) { Bitmap = new ConcurrentBitmap(1, mapped); @@ -163,8 +169,37 @@ namespace Ryujinx.Memory.Tracking RealEndAddress = realAddress + realSize; _tracking = tracking; - _regions = tracking.GetVirtualRegionsForHandle(address, size); - foreach (var region in _regions) + + _regions = tracking.GetVirtualRegionsForHandle(address, size, false); + _guestRegions = GetGuestRegions(tracking, address, size, flags); + _allRegions = new List(_regions.Count + _guestRegions.Count); + + InitializeRegions(); + } + + private List GetGuestRegions(MemoryTracking tracking, ulong address, ulong size, RegionFlags flags) + { + ulong guestAddress; + ulong guestSize; + + if (flags.HasFlag(RegionFlags.UnalignedAccess)) + { + (guestAddress, guestSize) = tracking.GetUnalignedSafeRegion(address, size); + } + else + { + (guestAddress, guestSize) = (address, size); + } + + return tracking.GetVirtualRegionsForHandle(guestAddress, guestSize, true); + } + + private void InitializeRegions() + { + _allRegions.AddRange(_regions); + _allRegions.AddRange(_guestRegions); + + foreach (var region in _allRegions) { region.Handles.Add(this); } @@ -321,7 +356,7 @@ namespace Ryujinx.Memory.Tracking lock (_tracking.TrackingLock) { - foreach (VirtualRegion region in _regions) + foreach (VirtualRegion region in _allRegions) { protectionChanged |= region.UpdateProtection(); } @@ -379,7 +414,7 @@ namespace Ryujinx.Memory.Tracking { lock (_tracking.TrackingLock) { - foreach (VirtualRegion region in _regions) + foreach (VirtualRegion region in _allRegions) { region.UpdateProtection(); } @@ -414,7 +449,16 @@ namespace Ryujinx.Memory.Tracking /// Virtual region to add as a child internal void AddChild(VirtualRegion region) { - _regions.Add(region); + if (region.Guest) + { + _guestRegions.Add(region); + } + else + { + _regions.Add(region); + } + + _allRegions.Add(region); } /// @@ -469,7 +513,7 @@ namespace Ryujinx.Memory.Tracking lock (_tracking.TrackingLock) { - foreach (VirtualRegion region in _regions) + foreach (VirtualRegion region in _allRegions) { region.RemoveHandle(this); } diff --git a/src/Ryujinx.Memory/Tracking/VirtualRegion.cs b/src/Ryujinx.Memory/Tracking/VirtualRegion.cs index 538e94fef5..bb087e9af0 100644 --- a/src/Ryujinx.Memory/Tracking/VirtualRegion.cs +++ b/src/Ryujinx.Memory/Tracking/VirtualRegion.cs @@ -13,10 +13,14 @@ namespace Ryujinx.Memory.Tracking private readonly MemoryTracking _tracking; private MemoryPermission _lastPermission; - public VirtualRegion(MemoryTracking tracking, ulong address, ulong size, MemoryPermission lastPermission = MemoryPermission.Invalid) : base(address, size) + public bool Guest { get; } + + public VirtualRegion(MemoryTracking tracking, ulong address, ulong size, bool guest, MemoryPermission lastPermission = MemoryPermission.Invalid) : base(address, size) { _lastPermission = lastPermission; _tracking = tracking; + + Guest = guest; } /// @@ -103,7 +107,7 @@ namespace Ryujinx.Memory.Tracking if (_lastPermission != permission) { - _tracking.ProtectVirtualRegion(this, permission); + _tracking.ProtectVirtualRegion(this, permission, Guest); _lastPermission = permission; return true; @@ -131,7 +135,7 @@ namespace Ryujinx.Memory.Tracking public override INonOverlappingRange Split(ulong splitAddress) { - VirtualRegion newRegion = new(_tracking, splitAddress, EndAddress - splitAddress, _lastPermission); + VirtualRegion newRegion = new(_tracking, splitAddress, EndAddress - splitAddress, Guest, _lastPermission); Size = splitAddress - Address; // The new region inherits all of our parents. diff --git a/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs b/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs index 5c8396e6d4..85a1ac02bc 100644 --- a/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs +++ b/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs @@ -107,7 +107,7 @@ namespace Ryujinx.Tests.Memory throw new NotImplementedException(); } - public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection) + public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest) { OnProtect?.Invoke(va, size, protection); } From 732db7581fdb11c87e7eea099fea8f9c153f98dd Mon Sep 17 00:00:00 2001 From: gdkchan Date: Thu, 14 Mar 2024 19:46:57 -0300 Subject: [PATCH 02/87] Consider Polygon as unsupported is triangle fans are unsupported on Vulkan (#6490) --- src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index 434545fe01..7d7c109525 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -781,7 +781,9 @@ namespace Ryujinx.Graphics.Vulkan { PrimitiveTopology.Quads => PrimitiveTopology.Triangles, PrimitiveTopology.QuadStrip => PrimitiveTopology.TriangleStrip, - PrimitiveTopology.TriangleFan => Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.NoTriangleFans) ? PrimitiveTopology.Triangles : topology, + PrimitiveTopology.TriangleFan or PrimitiveTopology.Polygon => Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.NoTriangleFans) + ? PrimitiveTopology.Triangles + : topology, _ => topology, }; } @@ -791,7 +793,7 @@ namespace Ryujinx.Graphics.Vulkan return topology switch { PrimitiveTopology.Quads => true, - PrimitiveTopology.TriangleFan => Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.NoTriangleFans), + PrimitiveTopology.TriangleFan or PrimitiveTopology.Polygon => Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.NoTriangleFans), _ => false, }; } From 1217a8e69b9b4feadb34c2d38209d765c9542819 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Thu, 14 Mar 2024 22:59:09 +0000 Subject: [PATCH 03/87] GPU: Rebind RTs if scale changes when binding textures (#6493) This fixes a longstanding issue with resolution scale that could result in flickering graphics, typically the first frame something is drawn, or on camera cuts in cutscenes. The root cause of the issue is that texture scale can be changed when binding textures or images. This typically happens because a texture becomes a view of a larger texture, such as a 400x225 texture becoming a view of a 800x450 texture with two levels. If the 400x225 texture is bound as a render target and has state [1x Undesired], but the storage texture is [2x Scaled], the render target texture's scale is changed to [2x Scaled] to match its new storage. This means the scale changed after the render target state was processed... This can cause a number of issues. When render target state is processed, texture scales are examined and potentially changed so that they are all the same value. If one texture is scaled, all textures must be. If one texture is blacklisted from scaling, all of them must be. This results in a single resolution scale value being assigned to the TextureManager, which also scales the scissor and viewport values. If the scale is chosen as 1x, and a later texture binding changes one of the textures to be 2x, the scale in TextureManager no longer matches all of the bound textures. What's worse, the scales in these textures could mismatch entirely. This typically results in the support buffer scale, viewport and scissor being wrong for at least one of the bound render targets. This PR fixes the issue by re-evaluating render target state if any scale mismatches the expected scale after texture bindings happen. This can actually cause scale to change again, so it must loop back to perform texture bindings again. This can happen as many times as it needs to, but I don't expect it to happen more than once. Problematic bindings will just result in a blacklist, which will propagate to other bound targets. --- .../Engine/Threed/StateUpdater.cs | 17 +++++++++++--- .../Image/TextureManager.cs | 22 ++++++++++++++++--- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs index 6b4ea89f39..b3eb62185a 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs @@ -343,11 +343,22 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed bool unalignedChanged = _currentSpecState.SetHasUnalignedStorageBuffer(_channel.BufferManager.HasUnalignedStorageBuffers); - if (!_channel.TextureManager.CommitGraphicsBindings(_shaderSpecState) || unalignedChanged) + bool scaleMismatch; + do { - // Shader must be reloaded. _vtgWritesRtLayer should not change. - UpdateShaderState(); + if (!_channel.TextureManager.CommitGraphicsBindings(_shaderSpecState, out scaleMismatch) || unalignedChanged) + { + // Shader must be reloaded. _vtgWritesRtLayer should not change. + UpdateShaderState(); + } + + if (scaleMismatch) + { + // Binding textures changed scale of the bound render targets, correct the render target scale and rebind. + UpdateRenderTargetState(); + } } + while (scaleMismatch); _channel.BufferManager.CommitGraphicsBindings(_drawState.DrawIndexed); } diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs index 3bf0beefdb..8c2a887271 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs @@ -360,15 +360,16 @@ namespace Ryujinx.Graphics.Gpu.Image /// Commits bindings on the graphics pipeline. /// /// Specialization state for the bound shader + /// True if there is a scale mismatch in the render targets, indicating they must be re-evaluated /// True if all bound textures match the current shader specialization state, false otherwise - public bool CommitGraphicsBindings(ShaderSpecializationState specState) + public bool CommitGraphicsBindings(ShaderSpecializationState specState, out bool scaleMismatch) { _texturePoolCache.Tick(); _samplerPoolCache.Tick(); bool result = _gpBindingsManager.CommitBindings(specState); - UpdateRenderTargets(); + scaleMismatch = UpdateRenderTargets(); return result; } @@ -426,9 +427,12 @@ namespace Ryujinx.Graphics.Gpu.Image /// /// Update host framebuffer attachments based on currently bound render target buffers. /// - public void UpdateRenderTargets() + /// True if there is a scale mismatch in the render targets, indicating they must be re-evaluated + public bool UpdateRenderTargets() { bool anyChanged = false; + float expectedScale = RenderTargetScale; + bool scaleMismatch = false; Texture dsTexture = _rtDepthStencil; ITexture hostDsTexture = null; @@ -448,6 +452,11 @@ namespace Ryujinx.Graphics.Gpu.Image { _rtHostDs = hostDsTexture; anyChanged = true; + + if (dsTexture != null && dsTexture.ScaleFactor != expectedScale) + { + scaleMismatch = true; + } } for (int index = 0; index < _rtColors.Length; index++) @@ -470,6 +479,11 @@ namespace Ryujinx.Graphics.Gpu.Image { _rtHostColors[index] = hostTexture; anyChanged = true; + + if (texture != null && texture.ScaleFactor != expectedScale) + { + scaleMismatch = true; + } } } @@ -477,6 +491,8 @@ namespace Ryujinx.Graphics.Gpu.Image { _context.Renderer.Pipeline.SetRenderTargets(_rtHostColors, _rtHostDs); } + + return scaleMismatch; } /// From 24068b023c0b8a1d8d9c74e9dcabcf48771231c3 Mon Sep 17 00:00:00 2001 From: TSRBerry <20988865+TSRBerry@users.noreply.github.com> Date: Sat, 16 Mar 2024 19:41:38 +0100 Subject: [PATCH 04/87] discord: Update ApplicationID (#6513) --- .../DiscordIntegrationModule.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs b/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs index 0b9439eaa9..bbece1e1d5 100644 --- a/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs +++ b/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs @@ -7,7 +7,7 @@ namespace Ryujinx.UI.Common public static class DiscordIntegrationModule { private const string Description = "A simple, experimental Nintendo Switch emulator."; - private const string CliendId = "568815339807309834"; + private const string ApplicationId = "1216775165866807456"; private static DiscordRpcClient _discordClient; private static RichPresence _discordPresenceMain; @@ -24,14 +24,14 @@ namespace Ryujinx.UI.Common Details = "Main Menu", State = "Idling", Timestamps = Timestamps.Now, - Buttons = new[] - { + Buttons = + [ new Button { Label = "Website", - Url = "https://ryujinx.org/", + Url = "https://ryujinx.org/", }, - }, + ], }; ConfigurationState.Instance.EnableDiscordIntegration.Event += Update; @@ -52,7 +52,7 @@ namespace Ryujinx.UI.Common // If we need to activate it and the client isn't active, initialize it if (evnt.NewValue && _discordClient == null) { - _discordClient = new DiscordRpcClient(CliendId); + _discordClient = new DiscordRpcClient(ApplicationId); _discordClient.Initialize(); _discordClient.SetPresence(_discordPresenceMain); @@ -74,14 +74,14 @@ namespace Ryujinx.UI.Common Details = $"Playing {titleName}", State = (titleId == "0000000000000000") ? "Homebrew" : titleId.ToUpper(), Timestamps = Timestamps.Now, - Buttons = new[] - { + Buttons = + [ new Button { Label = "Website", Url = "https://ryujinx.org/", }, - }, + ], }); } From 26026d1357f29df6a938e1e13d5b84d4cf890891 Mon Sep 17 00:00:00 2001 From: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> Date: Sat, 16 Mar 2024 18:46:03 +0000 Subject: [PATCH 05/87] Fix Title Update Manager not refreshing app list (#6507) --- src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs b/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs index f3ac69600e..732f410ae7 100644 --- a/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs @@ -1,4 +1,6 @@ +using Avalonia; using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Interactivity; using Avalonia.Styling; using FluentAvalonia.UI.Controls; @@ -59,9 +61,15 @@ namespace Ryujinx.Ava.UI.Windows { ViewModel.Save(); - if (VisualRoot is MainWindow window) + if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime al) { - window.LoadApplications(); + foreach (Window window in al.Windows) + { + if (window is MainWindow mainWindow) + { + mainWindow.LoadApplications(); + } + } } ((ContentDialog)Parent).Hide(); From e19e7622a34d7ef0dc39b36ad41297003fcb8be4 Mon Sep 17 00:00:00 2001 From: standstaff <163401255+standstaff@users.noreply.github.com> Date: Sun, 17 Mar 2024 02:49:54 +0800 Subject: [PATCH 06/87] chore: remove repetitive words (#6500) Signed-off-by: standstaff --- src/Ryujinx.Graphics.Gpu/Memory/VirtualRangeCache.cs | 2 +- .../Translation/Optimizations/Optimizer.cs | 2 +- src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs | 2 +- .../SoftwareKeyboard/SoftwareKeyboardRendererBase.cs | 4 ++-- src/Ryujinx.Memory/MemoryBlock.cs | 8 ++++---- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Ryujinx.Graphics.Gpu/Memory/VirtualRangeCache.cs b/src/Ryujinx.Graphics.Gpu/Memory/VirtualRangeCache.cs index 889f5c9caa..964507a218 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/VirtualRangeCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/VirtualRangeCache.cs @@ -173,7 +173,7 @@ namespace Ryujinx.Graphics.Gpu.Memory ShrinkOverlapsBufferIfNeeded(); - // If the the range is not properly aligned for sparse mapping, + // If the range is not properly aligned for sparse mapping, // let's just force it to a single range. // This might cause issues in some applications that uses sparse // mappings. diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs index 17427a5f90..ea06691ba9 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs @@ -322,7 +322,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations Operand lhs = operation.GetSource(0); Operand rhs = operation.GetSource(1); - // Check LHS of the the main multiplication operation. We expect an input being multiplied by gl_FragCoord.w. + // Check LHS of the main multiplication operation. We expect an input being multiplied by gl_FragCoord.w. if (lhs.AsgOp is not Operation attrMulOp || attrMulOp.Inst != (Instruction.FP32 | Instruction.Multiply)) { return; diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs index 3b1321c58e..d59ca7e0ec 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs @@ -136,7 +136,7 @@ namespace Ryujinx.Graphics.Vulkan { instance.EnumeratePhysicalDevices(out var physicalDevices).ThrowOnError(); - // First we try to pick the the user preferred GPU. + // First we try to pick the user preferred GPU. for (int i = 0; i < physicalDevices.Length; i++) { if (IsPreferredAndSuitableDevice(api, physicalDevices[i], surface, preferredGpuId)) diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs index 0b87f87adc..9e48568e13 100644 --- a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs @@ -466,7 +466,7 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard private void DrawPadButton(IImageProcessingContext context, PointF point, Image icon, string label, bool pressed, bool enabled) { - // Use relative positions so we can center the the entire drawing later. + // Use relative positions so we can center the entire drawing later. float iconX = 0; float iconY = 0; @@ -522,7 +522,7 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard { var labelRectangle = MeasureString(ControllerToggleText, _labelsTextFont); - // Use relative positions so we can center the the entire drawing later. + // Use relative positions so we can center the entire drawing later. float keyWidth = _keyModeIcon.Width; float keyHeight = _keyModeIcon.Height; diff --git a/src/Ryujinx.Memory/MemoryBlock.cs b/src/Ryujinx.Memory/MemoryBlock.cs index 7fe7862a91..59ee269bb0 100644 --- a/src/Ryujinx.Memory/MemoryBlock.cs +++ b/src/Ryujinx.Memory/MemoryBlock.cs @@ -174,7 +174,7 @@ namespace Ryujinx.Memory /// Starting offset of the range being read /// Span where the bytes being read will be copied to /// Throw when the memory block has already been disposed - /// Throw when the memory region specified for the the data is out of range + /// Throw when the memory region specified for the data is out of range [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Read(ulong offset, Span data) { @@ -188,7 +188,7 @@ namespace Ryujinx.Memory /// Offset where the data is located /// Data at the specified address /// Throw when the memory block has already been disposed - /// Throw when the memory region specified for the the data is out of range + /// Throw when the memory region specified for the data is out of range [MethodImpl(MethodImplOptions.AggressiveInlining)] public T Read(ulong offset) where T : unmanaged { @@ -201,7 +201,7 @@ namespace Ryujinx.Memory /// Starting offset of the range being written /// Span where the bytes being written will be copied from /// Throw when the memory block has already been disposed - /// Throw when the memory region specified for the the data is out of range + /// Throw when the memory region specified for the data is out of range [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Write(ulong offset, ReadOnlySpan data) { @@ -215,7 +215,7 @@ namespace Ryujinx.Memory /// Offset to write the data into /// Data to be written /// Throw when the memory block has already been disposed - /// Throw when the memory region specified for the the data is out of range + /// Throw when the memory region specified for the data is out of range [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Write(ulong offset, T data) where T : unmanaged { From 18df25f66f32cf342398763f33fbd193dacc6372 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 16 Mar 2024 20:02:18 +0100 Subject: [PATCH 07/87] nuget: bump the avalonia group with 2 updates (#6505) Bumps the avalonia group with 2 updates: [Avalonia.Svg](https://github.com/wieslawsoltes/Svg.Skia) and [Avalonia.Svg.Skia](https://github.com/wieslawsoltes/Svg.Skia). Updates `Avalonia.Svg` from 11.0.0.14 to 11.0.0.16 - [Release notes](https://github.com/wieslawsoltes/Svg.Skia/releases) - [Changelog](https://github.com/wieslawsoltes/Svg.Skia/blob/master/CHANGELOG.md) - [Commits](https://github.com/wieslawsoltes/Svg.Skia/commits) Updates `Avalonia.Svg.Skia` from 11.0.0.14 to 11.0.0.16 - [Release notes](https://github.com/wieslawsoltes/Svg.Skia/releases) - [Changelog](https://github.com/wieslawsoltes/Svg.Skia/blob/master/CHANGELOG.md) - [Commits](https://github.com/wieslawsoltes/Svg.Skia/commits) --- updated-dependencies: - dependency-name: Avalonia.Svg dependency-type: direct:production update-type: version-update:semver-patch dependency-group: avalonia - dependency-name: Avalonia.Svg.Skia dependency-type: direct:production update-type: version-update:semver-patch dependency-group: avalonia ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index c08e943576..083eaf3d5e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -8,8 +8,8 @@ - - + + From 50bdda5baafcdca2773b38e5896c611b7bcc3112 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 16 Mar 2024 20:13:30 +0100 Subject: [PATCH 08/87] nuget: bump Microsoft.IdentityModel.JsonWebTokens from 7.3.0 to 7.4.0 (#6366) Bumps [Microsoft.IdentityModel.JsonWebTokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 7.3.0 to 7.4.0. - [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases) - [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md) - [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/7.3.0...v7.4.0) --- updated-dependencies: - dependency-name: Microsoft.IdentityModel.JsonWebTokens dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 083eaf3d5e..a0b694ce10 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -20,7 +20,7 @@ - + From bb8c5ebae1cc9666cccd83106f592148e7aa4293 Mon Sep 17 00:00:00 2001 From: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> Date: Sat, 16 Mar 2024 19:34:26 +0000 Subject: [PATCH 09/87] Ava UI: Content Dialog Fixes (#6482) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Don’t use ContentDialogHelper when not necessary * Remove `ExtendClientAreaToDecorationsHint` --- src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml.cs | 1 - .../UI/Windows/DownloadableContentManagerWindow.axaml.cs | 2 +- src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml.cs b/src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml.cs index 2b12d72f1d..0e24102478 100644 --- a/src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml.cs @@ -9,7 +9,6 @@ namespace Ryujinx.Ava.UI.Windows { InitializeComponent(); - ExtendClientAreaToDecorationsHint = true; TransparencyLevelHint = new[] { WindowTransparencyLevel.Transparent }; WindowStartupLocation = WindowStartupLocation.Manual; SystemDecorations = SystemDecorations.None; diff --git a/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml.cs b/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml.cs index 0c02fa0f53..b9e2c7be34 100644 --- a/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml.cs @@ -47,7 +47,7 @@ namespace Ryujinx.Ava.UI.Windows contentDialog.Styles.Add(bottomBorder); - await ContentDialogHelper.ShowAsync(contentDialog); + await contentDialog.ShowAsync(); } private void SaveAndClose(object sender, RoutedEventArgs routedEventArgs) diff --git a/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs b/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs index 732f410ae7..f5e2503231 100644 --- a/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs @@ -49,7 +49,7 @@ namespace Ryujinx.Ava.UI.Windows contentDialog.Styles.Add(bottomBorder); - await ContentDialogHelper.ShowAsync(contentDialog); + await contentDialog.ShowAsync(); } private void Close(object sender, RoutedEventArgs e) From a0552fd78ba90c843e8af6c7dd040f1fc83d72e9 Mon Sep 17 00:00:00 2001 From: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> Date: Sun, 17 Mar 2024 01:27:14 +0000 Subject: [PATCH 10/87] Ava UI: Fix locale crash (#6385) * Fix locale crash * Apply suggestions from code review --------- Co-authored-by: Ac_K --- src/Ryujinx/Common/Locale/LocaleManager.cs | 54 +++++++++++++--------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/src/Ryujinx/Common/Locale/LocaleManager.cs b/src/Ryujinx/Common/Locale/LocaleManager.cs index e973fcc067..2d3deeaa38 100644 --- a/src/Ryujinx/Common/Locale/LocaleManager.cs +++ b/src/Ryujinx/Common/Locale/LocaleManager.cs @@ -30,28 +30,22 @@ namespace Ryujinx.Ava.Common.Locale Load(); } - public void Load() + private void Load() { - // Load the system Language Code. - string localeLanguageCode = CultureInfo.CurrentCulture.Name.Replace('-', '_'); + var localeLanguageCode = !string.IsNullOrEmpty(ConfigurationState.Instance.UI.LanguageCode.Value) ? + ConfigurationState.Instance.UI.LanguageCode.Value : CultureInfo.CurrentCulture.Name.Replace('-', '_'); - // If the view is loaded with the UI Previewer detached, then override it with the saved one or default. + // Load en_US as default, if the target language translation is missing or incomplete. + LoadDefaultLanguage(); + LoadLanguage(localeLanguageCode); + + // Save whatever we ended up with. if (Program.PreviewerDetached) { - if (!string.IsNullOrEmpty(ConfigurationState.Instance.UI.LanguageCode.Value)) - { - localeLanguageCode = ConfigurationState.Instance.UI.LanguageCode.Value; - } - else - { - localeLanguageCode = DefaultLanguageCode; - } + ConfigurationState.Instance.UI.LanguageCode.Value = _localeLanguageCode; + + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); } - - // Load en_US as default, if the target language translation is incomplete. - LoadDefaultLanguage(); - - LoadLanguage(localeLanguageCode); } public string this[LocaleKeys key] @@ -126,24 +120,42 @@ namespace Ryujinx.Ava.Common.Locale private void LoadDefaultLanguage() { - _localeDefaultStrings = LoadJsonLanguage(); + _localeDefaultStrings = LoadJsonLanguage(DefaultLanguageCode); } public void LoadLanguage(string languageCode) { - foreach (var item in LoadJsonLanguage(languageCode)) + var locale = LoadJsonLanguage(languageCode); + + if (locale == null) + { + _localeLanguageCode = DefaultLanguageCode; + locale = _localeDefaultStrings; + } + else + { + _localeLanguageCode = languageCode; + } + + foreach (var item in locale) { this[item.Key] = item.Value; } - _localeLanguageCode = languageCode; LocaleChanged?.Invoke(); } - private static Dictionary LoadJsonLanguage(string languageCode = DefaultLanguageCode) + private static Dictionary LoadJsonLanguage(string languageCode) { var localeStrings = new Dictionary(); string languageJson = EmbeddedResources.ReadAllText($"Ryujinx/Assets/Locales/{languageCode}.json"); + + if (languageJson == null) + { + // We were unable to find file for that language code. + return null; + } + var strings = JsonHelper.Deserialize(languageJson, CommonJsonContext.Default.StringDictionary); foreach (var item in strings) From d26ef2eec309a7a7b30b103c245044d1cdc08add Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 17 Mar 2024 02:55:11 +0100 Subject: [PATCH 11/87] nuget: bump Microsoft.CodeAnalysis.CSharp from 4.8.0 to 4.9.2 (#6397) Bumps [Microsoft.CodeAnalysis.CSharp](https://github.com/dotnet/roslyn) from 4.8.0 to 4.9.2. - [Release notes](https://github.com/dotnet/roslyn/releases) - [Changelog](https://github.com/dotnet/roslyn/blob/main/docs/Breaking%20API%20Changes.md) - [Commits](https://github.com/dotnet/roslyn/commits) --- updated-dependencies: - dependency-name: Microsoft.CodeAnalysis.CSharp dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index a0b694ce10..455735fc46 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -19,7 +19,7 @@ - + From 75a4ea337008741896a88b8aed7919d368b46ea9 Mon Sep 17 00:00:00 2001 From: Ac_K Date: Thu, 21 Mar 2024 01:07:27 +0100 Subject: [PATCH 12/87] New Crowdin updates (#6541) * New translations en_us.json (French) * New translations en_us.json (Spanish) * New translations en_us.json (Arabic) * New translations en_us.json (German) * New translations en_us.json (Greek) * New translations en_us.json (Hebrew) * New translations en_us.json (Italian) * New translations en_us.json (Japanese) * New translations en_us.json (Korean) * New translations en_us.json (Polish) * New translations en_us.json (Russian) * New translations en_us.json (Turkish) * New translations en_us.json (Ukrainian) * New translations en_us.json (Chinese Simplified) * New translations en_us.json (Chinese Traditional) * New translations en_us.json (Portuguese, Brazilian) * New translations en_us.json (Thai) * Add missing Thai language name * Add new languages * Enable RTL for Arabic --------- Co-authored-by: gdkchan --- src/Ryujinx/Assets/Locales/ar_SA.json | 668 ++++++++++++++++ src/Ryujinx/Assets/Locales/de_DE.json | 100 +-- src/Ryujinx/Assets/Locales/el_GR.json | 116 +-- src/Ryujinx/Assets/Locales/es_ES.json | 102 +-- src/Ryujinx/Assets/Locales/fr_FR.json | 214 +++--- src/Ryujinx/Assets/Locales/he_IL.json | 88 ++- src/Ryujinx/Assets/Locales/it_IT.json | 82 +- src/Ryujinx/Assets/Locales/ja_JP.json | 84 ++- src/Ryujinx/Assets/Locales/ko_KR.json | 84 ++- src/Ryujinx/Assets/Locales/pl_PL.json | 350 ++++----- src/Ryujinx/Assets/Locales/pt_BR.json | 128 ++-- src/Ryujinx/Assets/Locales/ru_RU.json | 284 +++---- src/Ryujinx/Assets/Locales/th_TH.json | 668 ++++++++++++++++ src/Ryujinx/Assets/Locales/tr_TR.json | 96 +-- src/Ryujinx/Assets/Locales/uk_UA.json | 240 +++--- src/Ryujinx/Assets/Locales/zh_CN.json | 532 ++++++------- src/Ryujinx/Assets/Locales/zh_TW.json | 840 +++++++++++---------- src/Ryujinx/Common/Locale/LocaleManager.cs | 2 +- src/Ryujinx/Ryujinx.csproj | 6 +- 19 files changed, 3102 insertions(+), 1582 deletions(-) create mode 100644 src/Ryujinx/Assets/Locales/ar_SA.json create mode 100644 src/Ryujinx/Assets/Locales/th_TH.json diff --git a/src/Ryujinx/Assets/Locales/ar_SA.json b/src/Ryujinx/Assets/Locales/ar_SA.json new file mode 100644 index 0000000000..1fac4850c7 --- /dev/null +++ b/src/Ryujinx/Assets/Locales/ar_SA.json @@ -0,0 +1,668 @@ +{ + "Language": "اَلْعَرَبِيَّةُ", + "MenuBarFileOpenApplet": "فتح التطبيق المُصغَّر", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "افتح تطبيق محرر الـMii المُصغَّر في الوضع المستقل", + "SettingsTabInputDirectMouseAccess": "الوصول المباشر للفأرة", + "SettingsTabSystemMemoryManagerMode": "وضع إدارة الذاكرة:", + "SettingsTabSystemMemoryManagerModeSoftware": "البرنامج", + "SettingsTabSystemMemoryManagerModeHost": "المُضيف (سريع)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "المضيف غير محدد (سريع، غير آمن)", + "SettingsTabSystemUseHypervisor": "استخدم الهايبرڤايزور", + "MenuBarFile": "_ملف", + "MenuBarFileOpenFromFile": "_تحميل تطبيق من ملف", + "MenuBarFileOpenUnpacked": "تحميل لعبه غير محزومه", + "MenuBarFileOpenEmuFolder": "فتح مجلّد Ryujinx", + "MenuBarFileOpenLogsFolder": "افتح مجلد السجلات", + "MenuBarFileExit": "_خروج", + "MenuBarOptions": "_خيارات", + "MenuBarOptionsToggleFullscreen": "وضع ملء الشاشة", + "MenuBarOptionsStartGamesInFullscreen": "ابدأ الألعاب في وضع ملء الشاشة", + "MenuBarOptionsStopEmulation": "إيقاف المحاكاة", + "MenuBarOptionsSettings": "الإعدادات", + "MenuBarOptionsManageUserProfiles": "إدارة الملفات الشخصية للمستخدم", + "MenuBarActions": "الإجراءات", + "MenuBarOptionsSimulateWakeUpMessage": "محاكاة رسالة الاستيقاظ", + "MenuBarActionsScanAmiibo": "فحص Amiibo", + "MenuBarTools": "الأدوات", + "MenuBarToolsInstallFirmware": "تثبيت البرامج الثابتة", + "MenuBarFileToolsInstallFirmwareFromFile": "تثبيت البرنامج الثابت من XCI أو ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "تثبيت برنامج ثابت من مجلد", + "MenuBarToolsManageFileTypes": "إدارة أنواع الملفات", + "MenuBarToolsInstallFileTypes": "تثبيت أنواع الملفات", + "MenuBarToolsUninstallFileTypes": "إزالة أنواع الملفات", + "MenuBarHelp": "_مساعدة", + "MenuBarHelpCheckForUpdates": "تحقق من التحديثات", + "MenuBarHelpAbout": "عن البرنامج", + "MenuSearch": "بحث...", + "GameListHeaderFavorite": "مفضلة", + "GameListHeaderIcon": "الأيقونة", + "GameListHeaderApplication": "الاسم", + "GameListHeaderDeveloper": "المطور", + "GameListHeaderVersion": "الإصدار", + "GameListHeaderTimePlayed": "وقت اللعب", + "GameListHeaderLastPlayed": "اخر تشغيل", + "GameListHeaderFileExtension": "امتداد الملف", + "GameListHeaderFileSize": "حجم الملف", + "GameListHeaderPath": "المسار", + "GameListContextMenuOpenUserSaveDirectory": "فتح مجلد حفظ المستخدم", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "يفتح المجلد الذي يحتوي على حفظ المستخدم للتطبيق", + "GameListContextMenuOpenDeviceSaveDirectory": "فتح مجلد حفظ الجهاز", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "يفتح المجلد الذي يحتوي على حفظ الجهاز للتطبيق", + "GameListContextMenuOpenBcatSaveDirectory": "فتح مجلد حفظ الـBCAT", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "يفتح المجلد الذي يحتوي على حفظ الـBCAT للتطبيق", + "GameListContextMenuManageTitleUpdates": "إدارة تحديثات العنوان", + "GameListContextMenuManageTitleUpdatesToolTip": "يفتح نافذة إدارة تحديث العنوان", + "GameListContextMenuManageDlc": "إدارة المحتوي الإضافي", + "GameListContextMenuManageDlcToolTip": "يفتح نافذة إدارة المحتوي الإضافي", + "GameListContextMenuCacheManagement": "إدارة ذاكرة التخزين المؤقت", + "GameListContextMenuCacheManagementPurgePptc": "إعادة بناء PPTC في قائمة الانتظار", + "GameListContextMenuCacheManagementPurgePptcToolTip": "تنشيط PPTC لإعادة البناء في وقت التمهيد عند بدء تشغيل اللعبة التالية", + "GameListContextMenuCacheManagementPurgeShaderCache": "إزالة ذاكرة التشغيل المؤقتة للمظللات ", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "حذف الذاكرة المؤقتة للمظللات الخاصة بالتطبيق", + "GameListContextMenuCacheManagementOpenPptcDirectory": "فتح مجلد PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "يفتح المجلد الذي يحتوي على الـPPTC للتطبيق", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "فتح مجلد الذاكرة المؤقتة للمظللات ", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "يفتح المجلد الذي يحتوي على ذاكرة التشغيل المؤقتة للمظللات الخاصة بالتطبيق", + "GameListContextMenuExtractData": "إستخراج البيانات", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "إستخراج قسم ExeFS من التكوين الحالي للتطبيقات (بما في ذلك التحديثات)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "استخراج قسم RomFS من التكوين الحالي للتطبيقات (بما في ذلك التحديثات)", + "GameListContextMenuExtractDataLogo": "شعار", + "GameListContextMenuExtractDataLogoToolTip": "استخراج قسم الشعار من التكوين الحالي للتطبيقات (بما في ذلك التحديثات)", + "GameListContextMenuCreateShortcut": "إنشاء اختصار للتطبيق", + "GameListContextMenuCreateShortcutToolTip": "قم بإنشاء اختصار لسطح المكتب لتشغيل التطبيق المحدد", + "GameListContextMenuCreateShortcutToolTipMacOS": "قم بإنشاء اختصار في مجلد تطبيقات نظام التشغيل MacOS الذي يقوم بتشغيل التطبيق المحدد", + "GameListContextMenuOpenModsDirectory": "افتح مجلد التعديلات", + "GameListContextMenuOpenModsDirectoryToolTip": "يفتح المجلد الذي يحتوي على تعديلات التطبيق", + "GameListContextMenuOpenSdModsDirectory": "افتح مجلد تعديلات Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "يفتح دليل Atmosphere لبطاقة SD البديلة الذي يحتوي على تعديلات التطبيق. مفيد للتعديلات التي تم تعبئتها للأجهزة الحقيقية.", + "StatusBarGamesLoaded": "{0}/{1} الألعاب التي تم تحميلها", + "StatusBarSystemVersion": "إصدار النظام: {0}", + "LinuxVmMaxMapCountDialogTitle": "الحد الأدنى لتعيينات الذاكرة المكتشفة", + "LinuxVmMaxMapCountDialogTextPrimary": "هل ترغب في زيادة قيمة vm.max_map_count إلى {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "قد تحاول بعض الألعاب إنشاء المزيد من تعيينات الذاكرة أكثر مما هو مسموح به حاليا. سيتحطم Ryujinx بمجرد تجاوز هذا الحد.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "نعم، حتى إعادة التشغيل التالية", + "LinuxVmMaxMapCountDialogButtonPersistent": "نعم، بشكل دائم", + "LinuxVmMaxMapCountWarningTextPrimary": "الحد الأقصى لمقدار تعيينات الذاكرة أقل من الموصى به.", + "LinuxVmMaxMapCountWarningTextSecondary": "القيمة الحالية لـ vm.max_map_count ({0}) أقل من {1}. قد تحاول بعض الألعاب إنشاء المزيد من تعيينات الذاكرة أكثر مما هو مسموح به حاليا. سيتحطم Ryujinx بمجرد تجاوز هذا الحد.\n\nقد ترغب في زيادة الحد اليدوي أو تثبيت pkexec، مما يسمح لـ Ryujinx بالمساعدة في ذلك.", + "Settings": "إعدادات", + "SettingsTabGeneral": "واجهة المستخدم", + "SettingsTabGeneralGeneral": "العامة", + "SettingsTabGeneralEnableDiscordRichPresence": "تمكين وجود ديسكورد الغني", + "SettingsTabGeneralCheckUpdatesOnLaunch": "التحقق من وجود تحديثات عند التشغيل", + "SettingsTabGeneralShowConfirmExitDialog": "إظهار مربع حوار \"تأكيد الخروج\"", + "SettingsTabGeneralHideCursor": "إخفاء المؤشر:", + "SettingsTabGeneralHideCursorNever": "مطلقاً", + "SettingsTabGeneralHideCursorOnIdle": "عند الخمول", + "SettingsTabGeneralHideCursorAlways": "دائماً", + "SettingsTabGeneralGameDirectories": "مجلدات الألعاب", + "SettingsTabGeneralAdd": "إضافة", + "SettingsTabGeneralRemove": "إزالة", + "SettingsTabSystem": "النظام", + "SettingsTabSystemCore": "النواة", + "SettingsTabSystemSystemRegion": "منطقة النظام:", + "SettingsTabSystemSystemRegionJapan": "اليابان", + "SettingsTabSystemSystemRegionUSA": "الولايات المتحدة الأمريكية", + "SettingsTabSystemSystemRegionEurope": "أوروبا", + "SettingsTabSystemSystemRegionAustralia": "أستراليا", + "SettingsTabSystemSystemRegionChina": "الصين", + "SettingsTabSystemSystemRegionKorea": "كوريا", + "SettingsTabSystemSystemRegionTaiwan": "تايوان", + "SettingsTabSystemSystemLanguage": "لغة النظام:", + "SettingsTabSystemSystemLanguageJapanese": "اليابانية", + "SettingsTabSystemSystemLanguageAmericanEnglish": "الإنجليزية الأمريكية", + "SettingsTabSystemSystemLanguageFrench": "الفرنسية", + "SettingsTabSystemSystemLanguageGerman": "الألمانية", + "SettingsTabSystemSystemLanguageItalian": "الإيطالية", + "SettingsTabSystemSystemLanguageSpanish": "الإسبانية", + "SettingsTabSystemSystemLanguageChinese": "الصينية", + "SettingsTabSystemSystemLanguageKorean": "الكورية", + "SettingsTabSystemSystemLanguageDutch": "الهولندية", + "SettingsTabSystemSystemLanguagePortuguese": "البرتغالية", + "SettingsTabSystemSystemLanguageRussian": "الروسية", + "SettingsTabSystemSystemLanguageTaiwanese": "التايوانية", + "SettingsTabSystemSystemLanguageBritishEnglish": "الإنجليزية البريطانية", + "SettingsTabSystemSystemLanguageCanadianFrench": "الفرنسية الكندية", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "إسبانية أمريكا اللاتينية", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "الصينية المبسطة", + "SettingsTabSystemSystemLanguageTraditionalChinese": "الصينية التقليدية", + "SettingsTabSystemSystemTimeZone": "نظام التوقيت للنظام:", + "SettingsTabSystemSystemTime": "توقيت النظام:", + "SettingsTabSystemEnableVsync": "VSync", + "SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "FS Integrity Checks", + "SettingsTabSystemAudioBackend": "خلفية الصوت:", + "SettingsTabSystemAudioBackendDummy": "زائف", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "الاختراقات", + "SettingsTabSystemHacksNote": "قد يتسبب في عدم الاستقرار", + "SettingsTabSystemExpandDramSize": "استخدام تخطيط الذاكرة البديل (المطورين)", + "SettingsTabSystemIgnoreMissingServices": "تجاهل الخدمات المفقودة", + "SettingsTabGraphics": "الرسوميات", + "SettingsTabGraphicsAPI": "الرسومات API", + "SettingsTabGraphicsEnableShaderCache": "تفعيل ذاكرة المظللات المؤقتة", + "SettingsTabGraphicsAnisotropicFiltering": "تصفية متباين الخواص:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "تلقائي", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4×", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "مقياس الدقة", + "SettingsTabGraphicsResolutionScaleCustom": "مخصص (لا ينصح به)", + "SettingsTabGraphicsResolutionScaleNative": "الأصل (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (لا ينصح به)", + "SettingsTabGraphicsAspectRatio": "نسبة الارتفاع إلى العرض:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "تمدد لتلائم حجم النافذة", + "SettingsTabGraphicsDeveloperOptions": "خيارات المطور", + "SettingsTabGraphicsShaderDumpPath": "مسار تفريغ الرسومات:", + "SettingsTabLogging": "التسجيل", + "SettingsTabLoggingLogging": "التسجيل", + "SettingsTabLoggingEnableLoggingToFile": "تفعيل التسجيل إلى ملف", + "SettingsTabLoggingEnableStubLogs": "تفعيل سجلات الـStub", + "SettingsTabLoggingEnableInfoLogs": "تفعيل سجلات المعلومات", + "SettingsTabLoggingEnableWarningLogs": "تفعيل سجلات التحذير", + "SettingsTabLoggingEnableErrorLogs": "تفعيل سجلات الأخطاء", + "SettingsTabLoggingEnableTraceLogs": "تفعيل سجلات التتبع", + "SettingsTabLoggingEnableGuestLogs": "تفعيل سجلات الضيوف", + "SettingsTabLoggingEnableFsAccessLogs": "Enable Fs Access Logs", + "SettingsTabLoggingFsGlobalAccessLogMode": "Fs Global Access Log Mode:", + "SettingsTabLoggingDeveloperOptions": "خيارات المطور", + "SettingsTabLoggingDeveloperOptionsNote": "تحذير: سوف يقلل من الأداء", + "SettingsTabLoggingGraphicsBackendLogLevel": "مستوى سجل خلفية الرسومات:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "لا شيء", + "SettingsTabLoggingGraphicsBackendLogLevelError": "خطأ", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "تباطؤ", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "الكل", + "SettingsTabLoggingEnableDebugLogs": "تمكين سجلات التصحيح", + "SettingsTabInput": "الإدخال", + "SettingsTabInputEnableDockedMode": "مركب بالمنصة", + "SettingsTabInputDirectKeyboardAccess": "الوصول المباشر إلى لوحة المفاتيح", + "SettingsButtonSave": "حفظ", + "SettingsButtonClose": "إغلاق", + "SettingsButtonOk": "موافق", + "SettingsButtonCancel": "إلغاء", + "SettingsButtonApply": "تطبيق", + "ControllerSettingsPlayer": "اللاعب", + "ControllerSettingsPlayer1": "اللاعب ١", + "ControllerSettingsPlayer2": "اللاعب 2", + "ControllerSettingsPlayer3": "اللاعب 3", + "ControllerSettingsPlayer4": "اللاعب 4", + "ControllerSettingsPlayer5": "اللاعب 5", + "ControllerSettingsPlayer6": "اللاعب 6", + "ControllerSettingsPlayer7": "اللاعب 7", + "ControllerSettingsPlayer8": "اللاعب 8", + "ControllerSettingsHandheld": "محمول", + "ControllerSettingsInputDevice": "جهاز الإدخال", + "ControllerSettingsRefresh": "تحديث", + "ControllerSettingsDeviceDisabled": "معطل", + "ControllerSettingsControllerType": "نوع ذراع التحكم", + "ControllerSettingsControllerTypeHandheld": "محمول", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "اقتران جوي كون", + "ControllerSettingsControllerTypeJoyConLeft": "يسار جوي كون", + "ControllerSettingsControllerTypeJoyConRight": "يمين جوي كون", + "ControllerSettingsProfile": "الملف الشخصي", + "ControllerSettingsProfileDefault": "افتراضي", + "ControllerSettingsLoad": "تحميل", + "ControllerSettingsAdd": "إضافة", + "ControllerSettingsRemove": "إزالة", + "ControllerSettingsButtons": "الأزرار", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "لوحة الاتجاه", + "ControllerSettingsDPadUp": "اعلى", + "ControllerSettingsDPadDown": "أسفل", + "ControllerSettingsDPadLeft": "يسار", + "ControllerSettingsDPadRight": "يمين", + "ControllerSettingsStickButton": "زر", + "ControllerSettingsStickUp": "اعلى", + "ControllerSettingsStickDown": "أسفل", + "ControllerSettingsStickLeft": "يسار", + "ControllerSettingsStickRight": "يمين", + "ControllerSettingsStickStick": "عصا", + "ControllerSettingsStickInvertXAxis": "عكس عرض العصا", + "ControllerSettingsStickInvertYAxis": "عكس أفق العصا", + "ControllerSettingsStickDeadzone": "المنطقة الميتة:", + "ControllerSettingsLStick": "العصا اليسرى", + "ControllerSettingsRStick": "العصا اليمنى", + "ControllerSettingsTriggersLeft": "المحفزات اليسرى", + "ControllerSettingsTriggersRight": "المحفزات اليمني", + "ControllerSettingsTriggersButtonsLeft": "أزرار التحفيز اليسرى", + "ControllerSettingsTriggersButtonsRight": "أزرار التحفيز اليمنى", + "ControllerSettingsTriggers": "المحفزات", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "الأزرار اليسار", + "ControllerSettingsExtraButtonsRight": "الأزرار اليمين", + "ControllerSettingsMisc": "إعدادات إضافية", + "ControllerSettingsTriggerThreshold": "قوة التحفيز:", + "ControllerSettingsMotion": "الحركة", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "استخدام الحركة المتوافقة مع CemuHook", + "ControllerSettingsMotionControllerSlot": "خانة ذراع التحكم:", + "ControllerSettingsMotionMirrorInput": "إعادة الإدخال", + "ControllerSettingsMotionRightJoyConSlot": "خانة اليمين JoyCon :", + "ControllerSettingsMotionServerHost": "مضيف الخادم:", + "ControllerSettingsMotionGyroSensitivity": "حساسية الغيرو:", + "ControllerSettingsMotionGyroDeadzone": "منطقة الغيرو الميتة:", + "ControllerSettingsSave": "حفظ", + "ControllerSettingsClose": "إغلاق", + "UserProfilesSelectedUserProfile": "الملف الشخصي المحدد للمستخدم:", + "UserProfilesSaveProfileName": "حفظ اسم الملف الشخصي", + "UserProfilesChangeProfileImage": "تغيير صورة الملف الشخصي", + "UserProfilesAvailableUserProfiles": "الملفات الشخصية للمستخدم المتاحة:", + "UserProfilesAddNewProfile": "أنشئ ملف شخصي", + "UserProfilesDelete": "حذف", + "UserProfilesClose": "إغلاق", + "ProfileNameSelectionWatermark": "اختر اسم مستعار", + "ProfileImageSelectionTitle": "تحديد صورة الملف الشخصي", + "ProfileImageSelectionHeader": "اختر صورة الملف الشخصي", + "ProfileImageSelectionNote": "يمكنك استيراد صورة ملف تعريف مخصصة، أو تحديد صورة رمزية من البرامج الثابتة للنظام", + "ProfileImageSelectionImportImage": "استيراد ملف الصورة", + "ProfileImageSelectionSelectAvatar": "حدد الصورة الرمزية للبرنامج الثابت", + "InputDialogTitle": "حوار الإدخال", + "InputDialogOk": "موافق", + "InputDialogCancel": "إلغاء", + "InputDialogAddNewProfileTitle": "اختر اسم الملف الشخصي", + "InputDialogAddNewProfileHeader": "الرجاء إدخال اسم الملف الشخصي", + "InputDialogAddNewProfileSubtext": "(الطول الأقصى: {0})", + "AvatarChoose": "اختر الصورة الرمزية", + "AvatarSetBackgroundColor": "تعيين لون الخلفية", + "AvatarClose": "إغلاق", + "ControllerSettingsLoadProfileToolTip": "تحميل الملف الشخصي", + "ControllerSettingsAddProfileToolTip": "إضافة ملف تعريف", + "ControllerSettingsRemoveProfileToolTip": "إزالة ملف التعريف", + "ControllerSettingsSaveProfileToolTip": "حفظ ملف التعريف", + "MenuBarFileToolsTakeScreenshot": "أخذ لقطة للشاشة", + "MenuBarFileToolsHideUi": "إخفاء واجهة المستخدم", + "GameListContextMenuRunApplication": "تشغيل التطبيق", + "GameListContextMenuToggleFavorite": "تبديل المفضلة", + "GameListContextMenuToggleFavoriteToolTip": "تبديل الحالة المفضلة للعبة", + "SettingsTabGeneralTheme": "السمة:", + "SettingsTabGeneralThemeDark": "داكن", + "SettingsTabGeneralThemeLight": "فاتح", + "ControllerSettingsConfigureGeneral": "ضبط", + "ControllerSettingsRumble": "الاهتزاز", + "ControllerSettingsRumbleStrongMultiplier": "مضاعف اهتزاز قوي", + "ControllerSettingsRumbleWeakMultiplier": "مضاعف اهتزاز ضعيف", + "DialogMessageSaveNotAvailableMessage": "لا يوجد حفظ لـ {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "هل ترغب في إنشاء حفظ لهذه اللعبة؟", + "DialogConfirmationTitle": "Ryujinx - تأكيد", + "DialogUpdaterTitle": "Ryujinx - تحديث", + "DialogErrorTitle": "Ryujinx - خطأ", + "DialogWarningTitle": "Ryujinx - تحذير", + "DialogExitTitle": "Ryujinx - الخروج", + "DialogErrorMessage": "واجه Ryujinx خطأ", + "DialogExitMessage": "هل أنت متأكد من أنك تريد إغلاق Ryujinx؟", + "DialogExitSubMessage": "سيتم فقدان كافة البيانات غير المحفوظة!", + "DialogMessageCreateSaveErrorMessage": "حدث خطأ أثناء إنشاء المحفوظة المحددة: {0}", + "DialogMessageFindSaveErrorMessage": "حدث خطأ أثناء البحث عن البيانات المحفوظة المحددة: {0}", + "FolderDialogExtractTitle": "اختر المجلد الذي تريد الاستخراج إليه", + "DialogNcaExtractionMessage": "استخراج قسم {0} من {1}...", + "DialogNcaExtractionTitle": "Ryujinx - مستخرج قسم NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "فشل الاستخراج. لم يكن NCA الرئيسي موجودًا في الملف المحدد.", + "DialogNcaExtractionCheckLogErrorMessage": "فشل الاستخراج. اقرأ ملف السجل لمزيد من المعلومات.", + "DialogNcaExtractionSuccessMessage": "تم الاستخراج بنجاح.", + "DialogUpdaterConvertFailedMessage": "فشل تحويل إصدار Ryujinx الحالي.", + "DialogUpdaterCancelUpdateMessage": "إلغاء التحديث!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "أنت تستخدم بالفعل أحدث إصدار من Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "حدث خطأ أثناء محاولة الحصول على معلومات الإصدار من إصدار GitHub. يمكن أن يحدث هذا إذا تم تجميع إصدار جديد بواسطة GitHub Actions. جرب مجددا بعد دقائق.", + "DialogUpdaterConvertFailedGithubMessage": "فشل تحويل إصدار Ryujinx المستلم من إصدار Github.", + "DialogUpdaterDownloadingMessage": "تحميل التحديث...", + "DialogUpdaterExtractionMessage": "استخراج التحديث...", + "DialogUpdaterRenamingMessage": "إعادة تسمية التحديث...", + "DialogUpdaterAddingFilesMessage": "إضافة تحديث جديد...", + "DialogUpdaterCompleteMessage": "اكتمل التحديث", + "DialogUpdaterRestartMessage": "هل تريد إعادة تشغيل Ryujinx الآن؟", + "DialogUpdaterNoInternetMessage": "أنت غير متصل بالإنترنت.", + "DialogUpdaterNoInternetSubMessage": "يرجى التحقق من أن لديك اتصال إنترنت فعال!", + "DialogUpdaterDirtyBuildMessage": "لا يمكنك تحديث البناء القذر من Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "الرجاء تحميل Ryujinx على https://ryujinx.org/ إذا كنت تبحث عن إصدار مدعوم.", + "DialogRestartRequiredMessage": "يتطلب إعادة التشغيل", + "DialogThemeRestartMessage": "تم حفظ السمة. إعادة التشغيل مطلوبة لتطبيق السمة.", + "DialogThemeRestartSubMessage": "هل تريد إعادة التشغيل", + "DialogFirmwareInstallEmbeddedMessage": "هل ترغب في تثبيت البرنامج الثابت المدمج في هذه اللعبة؟ (البرنامج الثابت {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "لم يتم العثور على أي برنامج ثابت مثبت ولكن Ryujinx كان قادراً على تثبيت البرنامج الثابت {0} من اللعبة المقدمة.\\nسيبدأ المحاكي الآن.", + "DialogFirmwareNoFirmwareInstalledMessage": "لا يوجد برنامج ثابت مثبت", + "DialogFirmwareInstalledMessage": "تم تثبيت البرنامج الثابت {0}", + "DialogInstallFileTypesSuccessMessage": "تم تثبيت أنواع الملفات بنجاح!", + "DialogInstallFileTypesErrorMessage": "فشل تثبيت أنواع الملفات.", + "DialogUninstallFileTypesSuccessMessage": "تم إلغاء تثبيت أنواع الملفات بنجاح!", + "DialogUninstallFileTypesErrorMessage": "فشل إلغاء تثبيت أنواع الملفات.", + "DialogOpenSettingsWindowLabel": "فتح نافذة الإعدادات", + "DialogControllerAppletTitle": "برنامج التحكم", + "DialogMessageDialogErrorExceptionMessage": "خطأ في عرض مربع حوار الرسالة: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "خطأ في عرض لوحة مفاتيح البرامج: {0}", + "DialogErrorAppletErrorExceptionMessage": "Error displaying ErrorApplet Dialog: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "لمزيد من المعلومات حول كيفية إصلاح هذا الخطأ، اتبع دليل الإعداد الخاص بنا.", + "DialogUserErrorDialogTitle": "Ryujinx Error ({0})", + "DialogAmiiboApiTitle": "أميبو API", + "DialogAmiiboApiFailFetchMessage": "حدث خطأ أثناء جلب المعلومات من واجهة برمجة التطبيقات.", + "DialogAmiiboApiConnectErrorMessage": "غير قادر على الاتصال بخادم API Amiibo. قد تكون الخدمة متوقفة أو قد تحتاج إلى التحقق من اتصال الإنترنت الخاص بك على الإنترنت.", + "DialogProfileInvalidProfileErrorMessage": "الملف الشخصي {0} غير متوافق مع نظام تكوين الإدخال الحالي.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "لا يمكن الكتابة فوق الملف الشخصي الافتراضي", + "DialogProfileDeleteProfileTitle": "حذف ملف التعريف", + "DialogProfileDeleteProfileMessage": "هذا الإجراء لا رجعة فيه، هل أنت متأكد من أنك تريد المتابعة؟", + "DialogWarning": "تحذير", + "DialogPPTCDeletionMessage": "أنت على وشك الإنتظار لإعادة بناء PTC على التمهيد التالي :\n\n{0}\n\nهل أنت متأكد من أنك تريد المتابعة؟", + "DialogPPTCDeletionErrorMessage": "خطأ في إزالة ذاكرة التخزين المؤقت PPTC في {0}: {1}", + "DialogShaderDeletionMessage": "أنت على وشك حذف ذاكرة التخزين المؤقت لـ Shader من أجل:\n\n{0}\n\nهل انت متأكد انك تريد المتابعة؟", + "DialogShaderDeletionErrorMessage": "Error purging Shader cache at {0}: {1}", + "DialogRyujinxErrorMessage": "واجه Ryujinx خطأ", + "DialogInvalidTitleIdErrorMessage": "خطأ في واجهة المستخدم: اللعبة المحددة لم يكن لديها معرف عنوان صالح", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "لم يتم العثور على فريموير للنظام صالح في {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "تثبيت البرنامج الثابت {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "سيتم تثبيت إصدار النظام {0}.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nهذا سيحل محل إصدار النظام الحالي {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\nهل تريد المتابعة؟", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "تثبيت البرنامج الثابت...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "تم تثبيت إصدار النظام {0} بنجاح.", + "DialogUserProfileDeletionWarningMessage": "لن تكون هناك ملفات تعريف أخرى لفتحها إذا تم حذف الملف الشخصي المحدد", + "DialogUserProfileDeletionConfirmMessage": "هل تريد حذف الملف الشخصي المحدد", + "DialogUserProfileUnsavedChangesTitle": "تحذير - التغييرات غير المحفوظة", + "DialogUserProfileUnsavedChangesMessage": "لقد قمت بإجراء تغييرات على ملف تعريف المستخدم هذا ولم يتم حفظها.", + "DialogUserProfileUnsavedChangesSubMessage": "هل تريد تجاهل التغييرات؟", + "DialogControllerSettingsModifiedConfirmMessage": "تم تحديث إعدادات وحدة التحكم الحالية.", + "DialogControllerSettingsModifiedConfirmSubMessage": "هل تريد الحفظ ؟", + "DialogLoadFileErrorMessage": "{0}. Errored File: {1}", + "DialogModAlreadyExistsMessage": "التعديل موجود بالفعل", + "DialogModInvalidMessage": "المجلد المحدد لا يحتوي على تعديل!", + "DialogModDeleteNoParentMessage": "فشل الحذف: لم يمكن العثور على المجلد الرئيسي للتعديل\"{0}\"!", + "DialogDlcNoDlcErrorMessage": "الملف المحدد لا يحتوي على DLC للعنوان المحدد!", + "DialogPerformanceCheckLoggingEnabledMessage": "لقد تم تمكين تسجيل التتبع، والذي تم تصميمه ليتم استخدامه من قبل المطورين فقط.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "للحصول على الأداء الأمثل، يوصى بتعطيل تسجيل التتبع. هل ترغب في تعطيل تسجيل التتبع الآن؟", + "DialogPerformanceCheckShaderDumpEnabledMessage": "لقد قمت بتمكين تفريغ التظليل، والذي تم تصميمه ليستخدمه المطورون فقط.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "For optimal performance, it's recommended to disable shader dumping. Would you like to disable shader dumping now?", + "DialogLoadAppGameAlreadyLoadedMessage": "تم تحميل لعبة بالفعل", + "DialogLoadAppGameAlreadyLoadedSubMessage": "الرجاء إيقاف المحاكاة أو إغلاق المحاكي قبل بدء لعبة أخرى.", + "DialogUpdateAddUpdateErrorMessage": "الملف المحدد لا يحتوي على تحديث للملف المحدد!", + "DialogSettingsBackendThreadingWarningTitle": "Warning - Backend Threading", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx must be restarted after changing this option for it to apply fully. Depending on your platform, you may need to manually disable your driver's own multithreading when using Ryujinx's.", + "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", + "SettingsTabGraphicsFeaturesOptions": "المميزات", + "SettingsTabGraphicsBackendMultithreading": "Graphics Backend Multithreading:", + "CommonAuto": "تلقائي", + "CommonOff": "إيقاف", + "CommonOn": "تشغيل", + "InputDialogYes": "نعم", + "InputDialogNo": "لا", + "DialogProfileInvalidProfileNameErrorMessage": "يحتوي اسم الملف على أحرف غير صالحة. يرجى المحاولة مرة أخرى.", + "MenuBarOptionsPauseEmulation": "إيقاف مؤقت", + "MenuBarOptionsResumeEmulation": "استئناف", + "AboutUrlTooltipMessage": "انقر لفتح موقع Ryujinx في متصفحك الافتراضي.", + "AboutDisclaimerMessage": "Ryujinx is not affiliated with Nintendo™,\nor any of its partners, in any way.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) is used\nin our Amiibo emulation.", + "AboutPatreonUrlTooltipMessage": "Click to open the Ryujinx Patreon page in your default browser.", + "AboutGithubUrlTooltipMessage": "Click to open the Ryujinx GitHub page in your default browser.", + "AboutDiscordUrlTooltipMessage": "Click to open an invite to the Ryujinx Discord server in your default browser.", + "AboutTwitterUrlTooltipMessage": "Click to open the Ryujinx Twitter page in your default browser.", + "AboutRyujinxAboutTitle": "حول:", + "AboutRyujinxAboutContent": "Ryujinx is an emulator for the Nintendo Switch™.\nPlease support us on Patreon.\nGet all the latest news on our Twitter or Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.", + "AboutRyujinxMaintainersTitle": "تم إصلاحها بواسطة:", + "AboutRyujinxMaintainersContentTooltipMessage": "انقر لفتح صفحة المساهمين في متصفحك الافتراضي.", + "AboutRyujinxSupprtersTitle": "مدعوم على باتريون بواسطة:", + "AmiiboSeriesLabel": "مجموعة أميبو", + "AmiiboCharacterLabel": "Character", + "AmiiboScanButtonLabel": "فحصه", + "AmiiboOptionsShowAllLabel": "إظهار كل أميبو", + "AmiiboOptionsUsRandomTagLabel": "Hack: Use Random tag Uuid", + "DlcManagerTableHeadingEnabledLabel": "مفعل", + "DlcManagerTableHeadingTitleIdLabel": "معرف العنوان", + "DlcManagerTableHeadingContainerPathLabel": "مسار الحاوية", + "DlcManagerTableHeadingFullPathLabel": "المسار كاملاً", + "DlcManagerRemoveAllButton": "حذف الكل", + "DlcManagerEnableAllButton": "تشغيل الكل", + "DlcManagerDisableAllButton": "تعطيل الكل", + "ModManagerDeleteAllButton": "حذف الكل", + "MenuBarOptionsChangeLanguage": "تغيير اللغة", + "MenuBarShowFileTypes": "إظهار أنواع الملفات", + "CommonSort": "فرز", + "CommonShowNames": "عرض الأسماء", + "CommonFavorite": "المفضلة", + "OrderAscending": "ترتيب تصاعدي", + "OrderDescending": "ترتيب تنازلي", + "SettingsTabGraphicsFeatures": "الميزات والتحسينات", + "ErrorWindowTitle": "نافذة الخطأ", + "ToggleDiscordTooltip": "Choose whether or not to display Ryujinx on your \"currently playing\" Discord activity", + "AddGameDirBoxTooltip": "أدخل دليل اللعبة لإضافته إلى القائمة", + "AddGameDirTooltip": "إضافة دليل اللعبة إلى القائمة", + "RemoveGameDirTooltip": "إزالة دليل اللعبة المحدد", + "CustomThemeCheckTooltip": "Use a custom Avalonia theme for the GUI to change the appearance of the emulator menus", + "CustomThemePathTooltip": "مسار سمة واجهة المستخدم المخصصة", + "CustomThemeBrowseTooltip": "تصفح للحصول على سمة واجهة المستخدم المخصصة", + "DockModeToggleTooltip": "Docked mode makes the emulated system behave as a docked Nintendo Switch. This improves graphical fidelity in most games. Conversely, disabling this will make the emulated system behave as a handheld Nintendo Switch, reducing graphics quality.\n\nConfigure player 1 controls if planning to use docked mode; configure handheld controls if planning to use handheld mode.\n\nLeave ON if unsure.", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", + "RegionTooltip": "تغيير منطقة النظام", + "LanguageTooltip": "تغيير لغة النظام", + "TimezoneTooltip": "تغيير المنطقة الزمنية للنظام", + "TimeTooltip": "تغيير وقت النظام", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", + "PptcToggleTooltip": "Saves translated JIT functions so that they do not need to be translated every time the game loads.\n\nReduces stuttering and significantly speeds up boot times after the first boot of a game.\n\nLeave ON if unsure.", + "FsIntegrityToggleTooltip": "Checks for corrupt files when booting a game, and if corrupt files are detected, displays a hash error in the log.\n\nHas no impact on performance and is meant to help troubleshooting.\n\nLeave ON if unsure.", + "AudioBackendTooltip": "Changes the backend used to render audio.\n\nSDL2 is the preferred one, while OpenAL and SoundIO are used as fallbacks. Dummy will have no sound.\n\nSet to SDL2 if unsure.", + "MemoryManagerTooltip": "Change how guest memory is mapped and accessed. Greatly affects emulated CPU performance.\n\nSet to HOST UNCHECKED if unsure.", + "MemoryManagerSoftwareTooltip": "Use a software page table for address translation. Highest accuracy but slowest performance.", + "MemoryManagerHostTooltip": "Directly map memory in the host address space. Much faster JIT compilation and execution.", + "MemoryManagerUnsafeTooltip": "Directly map memory, but do not mask the address within the guest address space before access. Faster, but at the cost of safety. The guest application can access memory from anywhere in Ryujinx, so only run programs you trust with this mode.", + "UseHypervisorTooltip": "Use Hypervisor instead of JIT. Greatly improves performance when available, but can be unstable in its current state.", + "DRamTooltip": "Utilizes an alternative MemoryMode layout to mimic a Switch development model.\n\nThis is only useful for higher-resolution texture packs or 4k resolution mods. Does NOT improve performance.\n\nLeave OFF if unsure.", + "IgnoreMissingServicesTooltip": "Ignores unimplemented Horizon OS services. This may help in bypassing crashes when booting certain games.\n\nLeave OFF if unsure.", + "GraphicsBackendThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.", + "GalThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.", + "ShaderCacheToggleTooltip": "Saves a disk shader cache which reduces stuttering in subsequent runs.\n\nLeave ON if unsure.", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", + "ResolutionScaleEntryTooltip": "Floating point resolution scale, such as 1.5. Non-integral scales are more likely to cause issues or crash.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", + "ShaderDumpPathTooltip": "Graphics Shaders Dump Path", + "FileLogTooltip": "حفظ تسجيل وحدة التحكم إلى ملف سجل على القرص. لا يؤثر على الأداء.", + "StubLogTooltip": "Prints stub log messages in the console. Does not affect performance.", + "InfoLogTooltip": "Prints info log messages in the console. Does not affect performance.", + "WarnLogTooltip": "طباعة رسائل سجل التحذير في وحدة التحكم. لا يؤثر على الأداء.", + "ErrorLogTooltip": "طباعة رسائل سجل الأخطاء في وحدة التحكم. لا يؤثر على الأداء.", + "TraceLogTooltip": "طباعة رسائل سجل التتبع في وحدة التحكم. لا يؤثر على الأداء.", + "GuestLogTooltip": "طباعة رسائل سجل الضيف في وحدة التحكم. لا يؤثر على الأداء.", + "FileAccessLogTooltip": "طباعة رسائل سجل الوصول إلى الملفات في وحدة التحكم.", + "FSAccessLogModeTooltip": "Enables FS access log output to the console. Possible modes are 0-3", + "DeveloperOptionTooltip": "استخدمه بعناية", + "OpenGlLogLevel": "يتطلب تمكين مستويات السجل المناسبة", + "DebugLogTooltip": "Prints debug log messages in the console.\n\nOnly use this if specifically instructed by a staff member, as it will make logs difficult to read and worsen emulator performance.", + "LoadApplicationFileTooltip": "افتح مستكشف الملفات لاختيار ملف متوافق مع Switch لتحميله", + "LoadApplicationFolderTooltip": "افتح مستكشف الملفات لاختيار تطبيق متوافق مع Switch للتحميل", + "OpenRyujinxFolderTooltip": "فتح مجلد نظام ملفات Ryujinx", + "OpenRyujinxLogsTooltip": "Opens the folder where logs are written to", + "ExitTooltip": "الخروج من Ryujinx", + "OpenSettingsTooltip": "فتح نافذة الإعدادات", + "OpenProfileManagerTooltip": "افتح نافذة إدارة ملفات تعريف المستخدمين", + "StopEmulationTooltip": "إيقاف محاكاة اللعبة الحالية والعودة إلى اختيار اللعبة", + "CheckUpdatesTooltip": "التحقق من وجود تحديثات لـ Ryujinx", + "OpenAboutTooltip": "فتح حول النافذة", + "GridSize": "حجم الشبكة", + "GridSizeTooltip": "تغيير حجم عناصر الشبكة", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "البرتغالية البرازيلية", + "AboutRyujinxContributorsButtonHeader": "رؤية جميع المساهمين", + "SettingsTabSystemAudioVolume": "الحجم:", + "AudioVolumeTooltip": "تغيير مستوى الصوت", + "SettingsTabSystemEnableInternetAccess": "Guest Internet Access/LAN Mode", + "EnableInternetAccessTooltip": "للسماح للتطبيق المحاكي بالاتصال بالإنترنت.\n\nالألعاب ذات وضع الشبكة المحلية يمكن الاتصال ببعضها البعض عندما يتم تمكين هذا النظام وتكون الأنظمة متصلة بنفس نقطة الوصول. هذا يشمل وحدات التحكم الحقيقية أيضًا.\n\nلا يسمح بالاتصال بخوادم Nintendo. قد يسبب الانهيار في بعض الألعاب التي تحاول الاتصال بالإنترنت.\n\nاتركه متوقفا إن لم يكن مؤكدا.", + "GameListContextMenuManageCheatToolTip": "إدارة الغش", + "GameListContextMenuManageCheat": "إدارة الغش", + "GameListContextMenuManageModToolTip": "إدارة التعديلات", + "GameListContextMenuManageMod": "إدارة التعديلات", + "ControllerSettingsStickRange": "نطاق:", + "DialogStopEmulationTitle": "Ryujinx - إيقاف المحاكاة", + "DialogStopEmulationMessage": "هل أنت متأكد أنك تريد إيقاف المحاكاة؟", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "الصوت", + "SettingsTabNetwork": "الشبكة", + "SettingsTabNetworkConnection": "اتصال الشبكة", + "SettingsTabCpuCache": "ذاكرة المعالج المؤقت", + "SettingsTabCpuMemory": "وضع المعالج", + "DialogUpdaterFlatpakNotSupportedMessage": "الرجاء تحديث Ryujinx عبر FlatHub.", + "UpdaterDisabledWarningTitle": "التحديث معطل!", + "ControllerSettingsRotate90": "تدوير 90 درجة في اتجاه عقارب الساعة", + "IconSize": "حجم الأيقونة", + "IconSizeTooltip": "تغيير حجم أيقونات اللعبة", + "MenuBarOptionsShowConsole": "عرض وحدة التحكم", + "ShaderCachePurgeError": "Error purging shader cache at {0}: {1}", + "UserErrorNoKeys": "المفاتيح غير موجودة", + "UserErrorNoFirmware": "لم يتم العثور على البرنامج الثابت", + "UserErrorFirmwareParsingFailed": "خطأ في تحليل البرنامج الثابت", + "UserErrorApplicationNotFound": "التطبيق غير موجود", + "UserErrorUnknown": "خطأ غير معروف", + "UserErrorUndefined": "خطأ غير محدد", + "UserErrorNoKeysDescription": "Ryujinx was unable to find your 'prod.keys' file", + "UserErrorNoFirmwareDescription": "Ryujinx was unable to find any firmwares installed", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx was unable to parse the provided firmware. This is usually caused by outdated keys.", + "UserErrorApplicationNotFoundDescription": "Ryujinx couldn't find a valid application at the given path.", + "UserErrorUnknownDescription": "حدث خطأ غير معروف!", + "UserErrorUndefinedDescription": "حدث خطأ غير محدد! لا ينبغي أن يحدث هذا، يرجى الاتصال بمطور!", + "OpenSetupGuideMessage": "فتح دليل الإعداد", + "NoUpdate": "لا يوجد تحديث", + "TitleUpdateVersionLabel": "الإصدار: {0}", + "RyujinxInfo": "Ryujinx - معلومات", + "RyujinxConfirm": "Ryujinx - تأكيد", + "FileDialogAllTypes": "كل الأنواع", + "Never": "مطلقاً", + "SwkbdMinCharacters": "Must be at least {0} characters long", + "SwkbdMinRangeCharacters": "Must be {0}-{1} characters long", + "SoftwareKeyboard": "لوحة المفاتيح البرمجية", + "SoftwareKeyboardModeNumeric": "يجب أن يكون 0-9 أو '.' فقط", + "SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only", + "SoftwareKeyboardModeASCII": "Must be ASCII text only", + "ControllerAppletControllers": "ذراع التحكم المدعومة:", + "ControllerAppletPlayers": "اللاعبين:", + "ControllerAppletDescription": "الإعدادات الحالية غير صالحة. افتح الإعدادات وأعد تكوين المدخلات الخاصة بك.", + "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.", + "UpdaterRenaming": "إعادة تسمية الملفات القديمة...", + "UpdaterRenameFailed": "التحديث غير قادر على إعادة تسمية الملف: {0}", + "UpdaterAddingFiles": "إضافة ملفات جديدة...", + "UpdaterExtracting": "استخراج التحديث...", + "UpdaterDownloading": "تحميل التحديث...", + "Game": "لعبة", + "Docked": "مركب بالمنصة", + "Handheld": "محمول", + "ConnectionError": "خطأ في الاتصال", + "AboutPageDeveloperListMore": "{0} والمزيد...", + "ApiError": "خطأ في API.", + "LoadingHeading": "جارٍ تحميل {0}", + "CompilingPPTC": "تجميع الـ PTC", + "CompilingShaders": "تجميع الظلال", + "AllKeyboards": "كل لوحات المفاتيح", + "OpenFileDialogTitle": "حدد ملف مدعوم لفتحه", + "OpenFolderDialogTitle": "حدد مجلدًا يحتوي على لعبة غير مضغوطة", + "AllSupportedFormats": "كل التنسيقات المدعومة", + "RyujinxUpdater": "تحديث Ryujinx", + "SettingsTabHotkeys": "مفاتيح الاختصار في لوحة المفاتيح", + "SettingsTabHotkeysHotkeys": "مفاتيح الاختصار في لوحة المفاتيح", + "SettingsTabHotkeysToggleVsyncHotkey": "تبديل VSync:", + "SettingsTabHotkeysScreenshotHotkey": "لقطة الشاشة:", + "SettingsTabHotkeysShowUiHotkey": "عرض واجهة المستخدم:", + "SettingsTabHotkeysPauseHotkey": "إيقاف مؤقت:", + "SettingsTabHotkeysToggleMuteHotkey": "كتم الصوت:", + "ControllerMotionTitle": "إعدادات التحكم بالحركة", + "ControllerRumbleTitle": "إعدادات الهزاز", + "SettingsSelectThemeFileDialogTitle": "حدد ملف السمة", + "SettingsXamlThemeFile": "Xaml Theme File", + "AvatarWindowTitle": "إدارة الحسابات - الصورة الرمزية", + "Amiibo": "أميبو", + "Unknown": "غير معروف", + "Usage": "الاستخدام", + "Writable": "قابل للكتابة", + "SelectDlcDialogTitle": "حدد ملفات DLC", + "SelectUpdateDialogTitle": "حدد ملفات التحديث", + "SelectModDialogTitle": "Select mod directory", + "UserProfileWindowTitle": "مدير ملفات تعريف المستخدمين", + "CheatWindowTitle": "مدير الغش", + "DlcWindowTitle": "إدارة المحتوى القابل للتنزيل لـ {0} ({1})", + "UpdateWindowTitle": "مدير تحديث العنوان", + "CheatWindowHeading": "الغش متوفر لـ {0} [{1}]", + "BuildId": "معرف البناء:", + "DlcWindowHeading": "المحتويات القابلة للتنزيل {0}", + "ModWindowHeading": "{0} تعديل", + "UserProfilesEditProfile": "تعديل المحددة", + "Cancel": "إلغاء", + "Save": "حفظ", + "Discard": "تجاهل", + "Paused": "متوقف مؤقتا", + "UserProfilesSetProfileImage": "تعيين صورة ملف التعريف", + "UserProfileEmptyNameError": "الاسم مطلوب", + "UserProfileNoImageError": "يجب تعيين صورة الملف الشخصي", + "GameUpdateWindowHeading": "إدارة التحديثات لـ {0} ({1})", + "SettingsTabHotkeysResScaleUpHotkey": "زيادة الدقة:", + "SettingsTabHotkeysResScaleDownHotkey": "تقليل الدقة:", + "UserProfilesName": "الاسم:", + "UserProfilesUserId": "معرف المستخدم:", + "SettingsTabGraphicsBackend": "خلفية الرسومات", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", + "SettingsEnableTextureRecompression": "تمكين إعادة ضغط التكستر", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", + "SettingsTabGraphicsPreferredGpu": "GPU المفضل", + "SettingsTabGraphicsPreferredGpuTooltip": "Select the graphics card that will be used with the Vulkan graphics backend.\n\nDoes not affect the GPU that OpenGL will use.\n\nSet to the GPU flagged as \"dGPU\" if unsure. If there isn't one, leave untouched.", + "SettingsAppRequiredRestartMessage": "مطلوب إعادة تشغيل Ryujinx", + "SettingsGpuBackendRestartMessage": "Graphics Backend or GPU settings have been modified. This will require a restart to be applied", + "SettingsGpuBackendRestartSubMessage": "\n\nهل تريد إعادة التشغيل الآن؟", + "RyujinxUpdaterMessage": "هل تريد تحديث Ryujinx إلى أحدث إصدار؟", + "SettingsTabHotkeysVolumeUpHotkey": "زيادة مستوى الصوت:", + "SettingsTabHotkeysVolumeDownHotkey": "تقليل الحجم:", + "SettingsEnableMacroHLE": "Enable Macro HLE", + "SettingsEnableMacroHLETooltip": "High-level emulation of GPU Macro code.\n\nImproves performance, but may cause graphical glitches in some games.\n\nLeave ON if unsure.", + "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", + "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", + "VolumeShort": "الحجم", + "UserProfilesManageSaves": "إدارة الحفظ", + "DeleteUserSave": "هل تريد حذف حفظ المستخدم لهذه اللعبة؟", + "IrreversibleActionNote": "هذا الإجراء لا يمكن التراجع عنه.", + "SaveManagerHeading": "إدارة الحفظ لـ {0} ({1})", + "SaveManagerTitle": "مدير الحفظ", + "Name": "الاسم", + "Size": "الحجم", + "Search": "البحث", + "UserProfilesRecoverLostAccounts": "استعادة الحسابات المفقودة", + "Recover": "استعادة", + "UserProfilesRecoverHeading": "تم العثور على الحفظ للحسابات التالية", + "UserProfilesRecoverEmptyList": "لا توجد ملفات تعريف لاستردادها", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", + "GraphicsAALabel": "تنعيم الحواف:", + "GraphicsScalingFilterLabel": "فلتر التكبير:", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterLevelLabel": "المستوى", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", + "SmaaLow": "SMAA منخفض", + "SmaaMedium": "SMAA متوسط", + "SmaaHigh": "SMAA عالي", + "SmaaUltra": "SMAA فائق", + "UserEditorTitle": "تعديل المستخدم", + "UserEditorTitleCreate": "إنشاء مستخدم", + "SettingsTabNetworkInterface": "واجهة الشبكة:", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", + "NetworkInterfaceDefault": "افتراضي", + "PackagingShaders": "Packaging Shaders", + "AboutChangelogButton": "عرض سجل التغييرات على GitHub", + "AboutChangelogButtonTooltipMessage": "انقر لفتح سجل التغيير لهذا الإصدار في متصفحك الافتراضي.", + "SettingsTabNetworkMultiplayer": "لعب جماعي", + "MultiplayerMode": "النمط:", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." +} diff --git a/src/Ryujinx/Assets/Locales/de_DE.json b/src/Ryujinx/Assets/Locales/de_DE.json index 7cdcdf5a2a..c70660532e 100644 --- a/src/Ryujinx/Assets/Locales/de_DE.json +++ b/src/Ryujinx/Assets/Locales/de_DE.json @@ -9,12 +9,12 @@ "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host ungeprüft (am schnellsten, unsicher)", "SettingsTabSystemUseHypervisor": "Hypervisor verwenden", "MenuBarFile": "_Datei", - "MenuBarFileOpenFromFile": "_Datei öffnen", + "MenuBarFileOpenFromFile": "Datei _öffnen", "MenuBarFileOpenUnpacked": "_Entpacktes Spiel öffnen", "MenuBarFileOpenEmuFolder": "Ryujinx-Ordner öffnen", "MenuBarFileOpenLogsFolder": "Logs-Ordner öffnen", "MenuBarFileExit": "_Beenden", - "MenuBarOptions": "Optionen", + "MenuBarOptions": "_Optionen", "MenuBarOptionsToggleFullscreen": "Vollbild", "MenuBarOptionsStartGamesInFullscreen": "Spiele im Vollbildmodus starten", "MenuBarOptionsStopEmulation": "Emulation beenden", @@ -23,14 +23,14 @@ "MenuBarActions": "_Aktionen", "MenuBarOptionsSimulateWakeUpMessage": "Aufwachnachricht simulieren", "MenuBarActionsScanAmiibo": "Amiibo scannen", - "MenuBarTools": "_Werkzeuge", + "MenuBarTools": "_Tools", "MenuBarToolsInstallFirmware": "Firmware installieren", "MenuBarFileToolsInstallFirmwareFromFile": "Firmware von einer XCI- oder einer ZIP-Datei installieren", "MenuBarFileToolsInstallFirmwareFromDirectory": "Firmware aus einem Verzeichnis installieren", "MenuBarToolsManageFileTypes": "Dateitypen verwalten", "MenuBarToolsInstallFileTypes": "Dateitypen installieren", "MenuBarToolsUninstallFileTypes": "Dateitypen deinstallieren", - "MenuBarHelp": "Hilfe", + "MenuBarHelp": "_Hilfe", "MenuBarHelpCheckForUpdates": "Nach Updates suchen", "MenuBarHelpAbout": "Über Ryujinx", "MenuSearch": "Suchen...", @@ -54,9 +54,7 @@ "GameListContextMenuManageTitleUpdatesToolTip": "Öffnet den Spiel-Update-Manager", "GameListContextMenuManageDlc": "Verwalten von DLC", "GameListContextMenuManageDlcToolTip": "Öffnet den DLC-Manager", - "GameListContextMenuOpenModsDirectory": "Mod-Verzeichnis öffnen", - "GameListContextMenuOpenModsDirectoryToolTip": "Öffnet das Verzeichnis, welches Mods für die Spiele beinhaltet", - "GameListContextMenuCacheManagement": "Cache Verwaltung", + "GameListContextMenuCacheManagement": "Cache-Verwaltung", "GameListContextMenuCacheManagementPurgePptc": "PPTC als ungültig markieren", "GameListContextMenuCacheManagementPurgePptcToolTip": "Markiert den PPTC als ungültig, sodass dieser beim nächsten Spielstart neu erstellt wird", "GameListContextMenuCacheManagementPurgeShaderCache": "Shader Cache löschen", @@ -72,6 +70,13 @@ "GameListContextMenuExtractDataRomFSToolTip": "Extrahiert das RomFS aus der aktuellen Anwendungskonfiguration (einschließlich Updates)", "GameListContextMenuExtractDataLogo": "Logo", "GameListContextMenuExtractDataLogoToolTip": "Extrahiert das Logo aus der aktuellen Anwendungskonfiguration (einschließlich Updates)", + "GameListContextMenuCreateShortcut": "Erstelle Anwendungsverknüpfung", + "GameListContextMenuCreateShortcutToolTip": "Erstelle eine Desktop-Verknüpfung die die gewählte Anwendung startet", + "GameListContextMenuCreateShortcutToolTipMacOS": "Erstellen Sie eine Verknüpfung im MacOS-Programme-Ordner, die die ausgewählte Anwendung startet", + "GameListContextMenuOpenModsDirectory": "Open Mods Directory", + "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", + "GameListContextMenuOpenSdModsDirectory": "Atmosphere-Mod-Verzeichnis öffnen", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", "StatusBarGamesLoaded": "{0}/{1} Spiele geladen", "StatusBarSystemVersion": "Systemversion: {0}", "LinuxVmMaxMapCountDialogTitle": "Niedriges Limit für Speicherzuordnungen erkannt", @@ -150,7 +155,7 @@ "SettingsTabGraphicsResolutionScaleNative": "Nativ (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Nicht empfohlen)", "SettingsTabGraphicsAspectRatio": "Bildseitenverhältnis:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -292,13 +297,9 @@ "GameListContextMenuRunApplication": "Anwendung ausführen", "GameListContextMenuToggleFavorite": "Als Favoriten hinzufügen/entfernen", "GameListContextMenuToggleFavoriteToolTip": "Aktiviert den Favoriten-Status des Spiels", - "SettingsTabGeneralTheme": "Design", - "SettingsTabGeneralThemeCustomTheme": "Pfad für das benutzerdefinierte Design", - "SettingsTabGeneralThemeBaseStyle": "Farbschema", - "SettingsTabGeneralThemeBaseStyleDark": "Dunkel", - "SettingsTabGeneralThemeBaseStyleLight": "Hell", - "SettingsTabGeneralThemeEnableCustomTheme": "Design für die Emulator-Benutzeroberfläche", - "ButtonBrowse": "Durchsuchen", + "SettingsTabGeneralTheme": "Design:", + "SettingsTabGeneralThemeDark": "Dunkel", + "SettingsTabGeneralThemeLight": "Hell", "ControllerSettingsConfigureGeneral": "Konfigurieren", "ControllerSettingsRumble": "Vibration", "ControllerSettingsRumbleStrongMultiplier": "Starker Vibrations-Multiplikator", @@ -322,7 +323,7 @@ "DialogNcaExtractionCheckLogErrorMessage": "Extraktion fehlgeschlagen. Überprüfe die Logs für weitere Informationen.", "DialogNcaExtractionSuccessMessage": "Extraktion erfolgreich abgeschlossen.", "DialogUpdaterConvertFailedMessage": "Die Konvertierung der aktuellen Ryujinx-Version ist fehlgeschlagen.", - "DialogUpdaterCancelUpdateMessage": "Download wird abgebrochen!", + "DialogUpdaterCancelUpdateMessage": "Update wird abgebrochen!", "DialogUpdaterAlreadyOnLatestVersionMessage": "Es wird bereits die aktuellste Version von Ryujinx benutzt", "DialogUpdaterFailedToGetVersionMessage": "Beim Versuch, Veröffentlichungs-Info von GitHub Release zu erhalten, ist ein Fehler aufgetreten. Dies kann aufgrund einer neuen Veröffentlichung, die gerade von GitHub Actions kompiliert wird, verursacht werden.", "DialogUpdaterConvertFailedGithubMessage": "Fehler beim Konvertieren der erhaltenen Ryujinx-Version von GitHub Release.", @@ -332,8 +333,6 @@ "DialogUpdaterAddingFilesMessage": "Update wird hinzugefügt...", "DialogUpdaterCompleteMessage": "Update abgeschlossen!", "DialogUpdaterRestartMessage": "Ryujinx jetzt neu starten?", - "DialogUpdaterArchNotSupportedMessage": "Eine nicht unterstützte Systemarchitektur wird benutzt!", - "DialogUpdaterArchNotSupportedSubMessage": "Nur 64-Bit-Systeme werden unterstützt!", "DialogUpdaterNoInternetMessage": "Es besteht keine Verbindung mit dem Internet!", "DialogUpdaterNoInternetSubMessage": "Bitte vergewissern, dass eine funktionierende Internetverbindung existiert!", "DialogUpdaterDirtyBuildMessage": "Inoffizielle Versionen von Ryujinx können nicht aktualisiert werden", @@ -385,7 +384,10 @@ "DialogUserProfileUnsavedChangesSubMessage": "Möchten Sie Ihre Änderungen wirklich verwerfen?", "DialogControllerSettingsModifiedConfirmMessage": "Die aktuellen Controller-Einstellungen wurden aktualisiert.", "DialogControllerSettingsModifiedConfirmSubMessage": "Controller-Einstellungen speichern?", - "DialogLoadNcaErrorMessage": "{0}. Fehlerhafte Datei: {1}", + "DialogLoadFileErrorMessage": "{0}. Fehlerhafte Datei: {1}", + "DialogModAlreadyExistsMessage": "Mod ist bereits vorhanden", + "DialogModInvalidMessage": "Das angegebene Verzeichnis enthält keine Mods!", + "DialogModDeleteNoParentMessage": "Löschen fehlgeschlagen: Das übergeordnete Verzeichnis für den Mod \"{0}\" konnte nicht gefunden werden!", "DialogDlcNoDlcErrorMessage": "Die angegebene Datei enthält keinen DLC für den ausgewählten Titel!", "DialogPerformanceCheckLoggingEnabledMessage": "Es wurde die Debug Protokollierung aktiviert", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Um eine optimale Leistung zu erzielen, wird empfohlen, die Debug Protokollierung zu deaktivieren. Debug Protokollierung jetzt deaktivieren?", @@ -396,6 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "Die angegebene Datei enthält keine Updates für den ausgewählten Titel!", "DialogSettingsBackendThreadingWarningTitle": "Warnung - Render Threading", "DialogSettingsBackendThreadingWarningMessage": "Ryujinx muss muss neu gestartet werden, damit die Änderungen wirksam werden. Abhängig von dem Betriebssystem muss möglicherweise das Multithreading des Treibers manuell deaktiviert werden, wenn Ryujinx verwendet wird.", + "DialogModManagerDeletionWarningMessage": "Du bist dabei, diesen Mod zu lösche. {0}\n\nMöchtest du wirklich fortfahren?", + "DialogModManagerDeletionAllWarningMessage": "Du bist dabei, alle Mods für diesen Titel zu löschen.\n\nMöchtest du wirklich fortfahren?", "SettingsTabGraphicsFeaturesOptions": "Erweiterungen", "SettingsTabGraphicsBackendMultithreading": "Grafik-Backend Multithreading:", "CommonAuto": "Auto", @@ -418,7 +422,7 @@ "AboutRyujinxMaintainersTitle": "Entwickelt von:", "AboutRyujinxMaintainersContentTooltipMessage": "Klicke hier, um die Liste der Mitwirkenden im Standardbrowser zu öffnen.", "AboutRyujinxSupprtersTitle": "Unterstützt auf Patreon von:", - "AmiiboSeriesLabel": "Amiibo Serie", + "AmiiboSeriesLabel": "Amiibo-Serie", "AmiiboCharacterLabel": "Charakter", "AmiiboScanButtonLabel": "Einscannen", "AmiiboOptionsShowAllLabel": "Zeige alle Amiibos", @@ -430,6 +434,7 @@ "DlcManagerRemoveAllButton": "Entferne alle", "DlcManagerEnableAllButton": "Alle aktivieren", "DlcManagerDisableAllButton": "Alle deaktivieren", + "ModManagerDeleteAllButton": "Alle löschen", "MenuBarOptionsChangeLanguage": "Sprache ändern", "MenuBarShowFileTypes": "Dateitypen anzeigen", "CommonSort": "Sortieren", @@ -447,13 +452,13 @@ "CustomThemePathTooltip": "Gibt den Pfad zum Design für die Emulator-Benutzeroberfläche an", "CustomThemeBrowseTooltip": "Ermöglicht die Suche nach einem benutzerdefinierten Design für die Emulator-Benutzeroberfläche", "DockModeToggleTooltip": "Im gedockten Modus verhält sich das emulierte System wie eine Nintendo Switch im TV Modus. Dies verbessert die grafische Qualität der meisten Spiele. Umgekehrt führt die Deaktivierung dazu, dass sich das emulierte System wie eine Nintendo Switch im Handheld Modus verhält, was die Grafikqualität beeinträchtigt.\n\nKonfiguriere das Eingabegerät für Spieler 1, um im Docked Modus zu spielen; konfiguriere das Controllerprofil via der Handheld Option, wenn geplant wird den Handheld Modus zu nutzen.\n\nIm Zweifelsfall AN lassen.", - "DirectKeyboardTooltip": "Aktiviert/Deaktiviert den \"Direkter Tastaturzugriff (HID) Unterstützung\" (Ermöglicht die Benutzung der Tastaur als Eingabegerät in Spielen)", - "DirectMouseTooltip": "Aktiviert/Deaktiviert den \"Direkten Mauszugriff (HID) Unterstützung\" (Ermöglicht die Benutzung der Maus als Eingabegerät in Spielen)", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", "RegionTooltip": "Ändert die Systemregion", "LanguageTooltip": "Ändert die Systemsprache", "TimezoneTooltip": "Ändert die Systemzeitzone", "TimeTooltip": "Ändert die Systemzeit", - "VSyncToggleTooltip": "Vertikale Synchronisierung der emulierten Konsole. Diese Option ist ein Frame-Limiter für die meisten Spiele; die Deaktivierung kann dazu führen, dass Spiele mit höherer Geschwindigkeit laufen, Ladebildschirme länger benötigen oder hängen bleiben.\n\nKann beim Spielen mit einem frei wählbaren Hotkey ein- und ausgeschaltet werden.\n\nIm Zweifelsfall AN lassen.", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", "PptcToggleTooltip": "Speichert übersetzte JIT-Funktionen, sodass jene nicht jedes Mal übersetzt werden müssen, wenn das Spiel geladen wird.\n\nVerringert Stottern und die Zeit beim zweiten und den darauffolgenden Startvorgängen eines Spiels erheblich.\n\nIm Zweifelsfall AN lassen.", "FsIntegrityToggleTooltip": "Prüft beim Startvorgang auf beschädigte Dateien und zeigt bei beschädigten Dateien einen Hash-Fehler (Hash Error) im Log an.\n\nDiese Einstellung hat keinen Einfluss auf die Leistung und hilft bei der Fehlersuche.\n\nIm Zweifelsfall AN lassen.", "AudioBackendTooltip": "Ändert das Backend, das zum Rendern von Audio verwendet wird.\n\nSDL2 ist das bevorzugte Audio-Backend, OpenAL und SoundIO sind als Alternativen vorhanden. Dummy wird keinen Audio-Output haben.\n\nIm Zweifelsfall SDL2 auswählen.", @@ -467,10 +472,10 @@ "GraphicsBackendThreadingTooltip": "Führt Grafik-Backend Befehle auf einem zweiten Thread aus.\n\nDies beschleunigt die Shader-Kompilierung, reduziert Stottern und verbessert die Leistung auf GPU-Treibern ohne eigene Multithreading-Unterstützung. Geringfügig bessere Leistung bei Treibern mit Multithreading.\n\nIm Zweifelsfall auf AUTO stellen.", "GalThreadingTooltip": "Führt Grafik-Backend Befehle auf einem zweiten Thread aus.\n\nDies Beschleunigt die Shader-Kompilierung, reduziert Stottern und verbessert die Leistung auf GPU-Treibern ohne eigene Multithreading-Unterstützung. Geringfügig bessere Leistung bei Treibern mit Multithreading.\n\nIm Zweifelsfall auf auf AUTO stellen.", "ShaderCacheToggleTooltip": "Speichert einen persistenten Shader Cache, der das Stottern bei nachfolgenden Durchläufen reduziert.\n\nIm Zweifelsfall AN lassen.", - "ResolutionScaleTooltip": "Wendet die Auflösungsskalierung auf anwendbare Render Ziele", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", "ResolutionScaleEntryTooltip": "Fließkomma Auflösungsskalierung, wie 1,5.\n Bei nicht ganzzahligen Werten ist die Wahrscheinlichkeit größer, dass Probleme entstehen, die auch zum Absturz führen können.", - "AnisotropyTooltip": "Stufe der Anisotropen Filterung (Auf Auto setzen, um den vom Spiel geforderten Wert zu verwenden)", - "AspectRatioTooltip": "Auf das Renderer-Fenster angewandtes Seitenverhältnis.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", "ShaderDumpPathTooltip": "Grafik-Shader-Dump-Pfad", "FileLogTooltip": "Speichert die Konsolenausgabe in einer Log-Datei auf der Festplatte. Hat keinen Einfluss auf die Leistung.", "StubLogTooltip": "Ausgabe von Stub-Logs in der Konsole. Hat keinen Einfluss auf die Leistung.", @@ -504,6 +509,8 @@ "EnableInternetAccessTooltip": "Erlaubt es der emulierten Anwendung sich mit dem Internet zu verbinden.\n\nSpiele die den LAN-Modus unterstützen, ermöglichen es Ryujinx sich sowohl mit anderen Ryujinx-Systemen, als auch mit offiziellen Nintendo Switch Konsolen zu verbinden. Allerdings nur, wenn diese Option aktiviert ist und die Systeme mit demselben lokalen Netzwerk verbunden sind.\n\nDies erlaubt KEINE Verbindung zu Nintendo-Servern. Kann bei bestimmten Spielen die versuchen sich mit dem Internet zu verbinden zum Absturz führen.\n\nIm Zweifelsfall AUS lassen", "GameListContextMenuManageCheatToolTip": "Öffnet den Cheat-Manager", "GameListContextMenuManageCheat": "Cheats verwalten", + "GameListContextMenuManageModToolTip": "Mods verwalten", + "GameListContextMenuManageMod": "Mods verwalten", "ControllerSettingsStickRange": "Bereich:", "DialogStopEmulationTitle": "Ryujinx - Beende Emulation", "DialogStopEmulationMessage": "Emulation wirklich beenden?", @@ -513,10 +520,8 @@ "SettingsTabNetworkConnection": "Netwerkverbindung", "SettingsTabCpuCache": "CPU-Cache", "SettingsTabCpuMemory": "CPU-Speicher", - "DialogUpdaterFlatpakNotSupportedMessage": "Bitte aktualisiere Ryujinx mit FlatHub", + "DialogUpdaterFlatpakNotSupportedMessage": "Bitte aktualisiere Ryujinx über FlatHub", "UpdaterDisabledWarningTitle": "Updater deaktiviert!", - "GameListContextMenuOpenSdModsDirectory": "Atmosphere-Mod-Verzeichnis öffnen", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Öffnet das alternative SD-Karten-Atmosphere-Verzeichnis, das die Mods der Anwendung enthält. Dieser Ordner ist nützlich für Mods, die für einen gemoddete Switch erstellt worden sind.", "ControllerSettingsRotate90": "Um 90° rotieren", "IconSize": "Cover Größe", "IconSizeTooltip": "Ändert die Größe der Spiel-Cover", @@ -544,12 +549,13 @@ "SwkbdMinCharacters": "Muss mindestens {0} Zeichen lang sein", "SwkbdMinRangeCharacters": "Muss {0}-{1} Zeichen lang sein", "SoftwareKeyboard": "Software-Tastatur", - "SoftwareKeyboardModeNumbersOnly": "Nur Zahlen", + "SoftwareKeyboardModeNumeric": "Darf nur 0-9 oder \".\" sein", "SoftwareKeyboardModeAlphabet": "Keine CJK-Zeichen", "SoftwareKeyboardModeASCII": "Nur ASCII-Text", - "DialogControllerAppletMessagePlayerRange": "Die Anwendung benötigt {0} Spieler mit:\n\nTYPEN: {1}\n\nSPIELER: {2}\n\n{3}Bitte öffne die Einstellungen und rekonfiguriere die Controller Einstellungen oder drücke auf schließen.", - "DialogControllerAppletMessage": "Die Anwendung benötigt genau {0} Speieler mit:\n\nTYPEN: {1}\n\nSPIELER: {2}\n\n{3}Bitte öffne die Einstellungen und rekonfiguriere die Controller Einstellungen oder drücke auf schließen.", - "DialogControllerAppletDockModeSet": "Der 'Docked Modus' ist ausgewählt. Handheld ist ebenfalls ungültig.\n\n", + "ControllerAppletControllers": "Unterstützte Controller:", + "ControllerAppletPlayers": "Spieler:", + "ControllerAppletDescription": "Ihre aktuelle Konfiguration ist ungültig. Öffnen Sie die Einstellungen und konfigurieren Sie Ihre Eingaben neu.", + "ControllerAppletDocked": "Andockmodus gesetzt. Handheld-Steuerung sollte deaktiviert worden sein.", "UpdaterRenaming": "Alte Dateien umbenennen...", "UpdaterRenameFailed": "Der Updater konnte die folgende Datei nicht umbenennen: {0}", "UpdaterAddingFiles": "Neue Dateien hinzufügen...", @@ -569,8 +575,8 @@ "OpenFolderDialogTitle": "Wähle einen Ordner mit einem entpackten Spiel", "AllSupportedFormats": "Alle unterstützten Formate", "RyujinxUpdater": "Ryujinx - Updater", - "SettingsTabHotkeys": "Tastatur Hotkeys", - "SettingsTabHotkeysHotkeys": "Tastatur Hotkeys", + "SettingsTabHotkeys": "Tastatur-Hotkeys", + "SettingsTabHotkeysHotkeys": "Tastatur-Hotkeys", "SettingsTabHotkeysToggleVsyncHotkey": "VSync:", "SettingsTabHotkeysScreenshotHotkey": "Screenshot:", "SettingsTabHotkeysShowUiHotkey": "Zeige UI:", @@ -587,6 +593,7 @@ "Writable": "Beschreibbar", "SelectDlcDialogTitle": "DLC-Dateien auswählen", "SelectUpdateDialogTitle": "Update-Datei auswählen", + "SelectModDialogTitle": "Mod-Ordner auswählen", "UserProfileWindowTitle": "Benutzerprofile verwalten", "CheatWindowTitle": "Spiel-Cheats verwalten", "DlcWindowTitle": "Spiel-DLC verwalten", @@ -594,10 +601,12 @@ "CheatWindowHeading": "Cheats verfügbar für {0} [{1}]", "BuildId": "BuildId:", "DlcWindowHeading": "DLC verfügbar für {0} [{1}]", + "ModWindowHeading": "{0} Mod(s)", "UserProfilesEditProfile": "Profil bearbeiten", "Cancel": "Abbrechen", "Save": "Speichern", "Discard": "Verwerfen", + "Paused": "Pausiert", "UserProfilesSetProfileImage": "Profilbild einrichten", "UserProfileEmptyNameError": "Name ist erforderlich", "UserProfileNoImageError": "Bitte ein Profilbild auswählen", @@ -605,11 +614,11 @@ "SettingsTabHotkeysResScaleUpHotkey": "Auflösung erhöhen:", "SettingsTabHotkeysResScaleDownHotkey": "Auflösung verringern:", "UserProfilesName": "Name:", - "UserProfilesUserId": "Benutzer Id:", + "UserProfilesUserId": "Benutzer-ID:", "SettingsTabGraphicsBackend": "Grafik-Backend:", - "SettingsTabGraphicsBackendTooltip": "Verwendendetes Grafik-Backend", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", "SettingsEnableTextureRecompression": "Textur-Rekompression", - "SettingsEnableTextureRecompressionTooltip": "Komprimiert bestimmte Texturen, um den VRAM-Verbrauch zu reduzieren.\n\nEmpfohlen für die Verwendung von GPUs, die weniger als 4 GiB VRAM haben.\n\nIm Zweifelsfall AUS lassen", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", "SettingsTabGraphicsPreferredGpu": "Bevorzugte GPU:", "SettingsTabGraphicsPreferredGpuTooltip": "Wähle die Grafikkarte aus, die mit dem Vulkan Grafik-Backend verwendet werden soll.\n\nDies hat keinen Einfluss auf die GPU die OpenGL verwendet.\n\nIm Zweifelsfall die als \"dGPU\" gekennzeichnete GPU auswählen. Diese Einstellung unberührt lassen, wenn keine zur Auswahl steht.", "SettingsAppRequiredRestartMessage": "Ein Neustart von Ryujinx ist erforderlich", @@ -635,12 +644,12 @@ "Recover": "Wiederherstellen", "UserProfilesRecoverHeading": "Speicherstände wurden für die folgenden Konten gefunden", "UserProfilesRecoverEmptyList": "Keine Profile zum Wiederherstellen", - "GraphicsAATooltip": "Wendet Anti-Aliasing auf das Spiel-Rendering an", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", "GraphicsAALabel": "Antialiasing:", "GraphicsScalingFilterLabel": "Skalierungsfilter:", - "GraphicsScalingFilterTooltip": "Ermöglicht Framebuffer-Skalierung", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", "GraphicsScalingFilterLevelLabel": "Stufe", - "GraphicsScalingFilterLevelTooltip": "Skalierungsfilter-Stufe festlegen", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "SMAA Niedrig", "SmaaMedium": "SMAA Mittel", "SmaaHigh": "SMAA Hoch", @@ -648,9 +657,12 @@ "UserEditorTitle": "Nutzer bearbeiten", "UserEditorTitleCreate": "Nutzer erstellen", "SettingsTabNetworkInterface": "Netzwerkschnittstelle:", - "NetworkInterfaceTooltip": "Die Netzwerkschnittstelle, die für LAN-Funktionen verwendet wird", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", "NetworkInterfaceDefault": "Standard", "PackagingShaders": "Verpackt Shader", "AboutChangelogButton": "Changelog in GitHub öffnen", - "AboutChangelogButtonTooltipMessage": "Klicke hier, um das Changelog für diese Version in Ihrem Standardbrowser zu öffnen." -} \ No newline at end of file + "AboutChangelogButtonTooltipMessage": "Klicke hier, um das Changelog für diese Version in Ihrem Standardbrowser zu öffnen.", + "SettingsTabNetworkMultiplayer": "Mehrspieler", + "MultiplayerMode": "Modus:", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." +} diff --git a/src/Ryujinx/Assets/Locales/el_GR.json b/src/Ryujinx/Assets/Locales/el_GR.json index 59f50ad923..5b7e3b97d6 100644 --- a/src/Ryujinx/Assets/Locales/el_GR.json +++ b/src/Ryujinx/Assets/Locales/el_GR.json @@ -14,7 +14,7 @@ "MenuBarFileOpenEmuFolder": "Άνοιγμα Φακέλου Ryujinx", "MenuBarFileOpenLogsFolder": "Άνοιγμα Φακέλου Καταγραφής", "MenuBarFileExit": "_Έξοδος", - "MenuBarOptions": "Επιλογές", + "MenuBarOptions": "_Επιλογές", "MenuBarOptionsToggleFullscreen": "Λειτουργία Πλήρους Οθόνης", "MenuBarOptionsStartGamesInFullscreen": "Εκκίνηση Παιχνιδιών σε Πλήρη Οθόνη", "MenuBarOptionsStopEmulation": "Διακοπή Εξομοίωσης", @@ -30,7 +30,7 @@ "MenuBarToolsManageFileTypes": "Διαχείριση τύπων αρχείων", "MenuBarToolsInstallFileTypes": "Εγκαταστήσετε τύπους αρχείων.", "MenuBarToolsUninstallFileTypes": "Απεγκαταστήσετε τύπους αρχείων", - "MenuBarHelp": "Βοήθεια", + "MenuBarHelp": "_Βοήθεια", "MenuBarHelpCheckForUpdates": "Έλεγχος για Ενημερώσεις", "MenuBarHelpAbout": "Σχετικά με", "MenuSearch": "Αναζήτηση...", @@ -54,8 +54,6 @@ "GameListContextMenuManageTitleUpdatesToolTip": "Ανοίγει το παράθυρο διαχείρισης Ενημερώσεων Παιχνιδιού", "GameListContextMenuManageDlc": "Διαχείριση DLC", "GameListContextMenuManageDlcToolTip": "Ανοίγει το παράθυρο διαχείρισης DLC", - "GameListContextMenuOpenModsDirectory": "Άνοιγμα Τοποθεσίας Τροποποιήσεων", - "GameListContextMenuOpenModsDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει τις Τροποποιήσεις της εφαρμογής", "GameListContextMenuCacheManagement": "Διαχείριση Προσωρινής Μνήμης", "GameListContextMenuCacheManagementPurgePptc": "Εκκαθάριση Προσωρινής Μνήμης PPTC", "GameListContextMenuCacheManagementPurgePptcToolTip": "Διαγράφει την προσωρινή μνήμη PPTC της εφαρμογής", @@ -72,15 +70,22 @@ "GameListContextMenuExtractDataRomFSToolTip": "Εξαγωγή της ενότητας RomFS από την τρέχουσα διαμόρφωση της εφαρμογής (συμπεριλαμβανομένου ενημερώσεων)", "GameListContextMenuExtractDataLogo": "Λογότυπο", "GameListContextMenuExtractDataLogoToolTip": "Εξαγωγή της ενότητας Logo από την τρέχουσα διαμόρφωση της εφαρμογής (συμπεριλαμβανομένου ενημερώσεων)", + "GameListContextMenuCreateShortcut": "Δημιουργία Συντόμευσης Εφαρμογής", + "GameListContextMenuCreateShortcutToolTip": "Δημιουργία συντόμευσης επιφάνειας εργασίας που ανοίγει την επιλεγμένη εφαρμογή", + "GameListContextMenuCreateShortcutToolTipMacOS": "Create a shortcut in macOS's Applications folder that launches the selected Application", + "GameListContextMenuOpenModsDirectory": "Open Mods Directory", + "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", + "GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", "StatusBarGamesLoaded": "{0}/{1} Φορτωμένα Παιχνίδια", "StatusBarSystemVersion": "Έκδοση Συστήματος: {0}", - "LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected", - "LinuxVmMaxMapCountDialogTextPrimary": "Would you like to increase the value of vm.max_map_count to {0}", - "LinuxVmMaxMapCountDialogTextSecondary": "Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.", - "LinuxVmMaxMapCountDialogButtonUntilRestart": "Yes, until the next restart", - "LinuxVmMaxMapCountDialogButtonPersistent": "Yes, permanently", - "LinuxVmMaxMapCountWarningTextPrimary": "Max amount of memory mappings is lower than recommended.", - "LinuxVmMaxMapCountWarningTextSecondary": "The current value of vm.max_map_count ({0}) is lower than {1}. Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.\n\nYou might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that.", + "LinuxVmMaxMapCountDialogTitle": "Εντοπίστηκε χαμηλό όριο για αντιστοιχίσεις μνήμης", + "LinuxVmMaxMapCountDialogTextPrimary": "Θα θέλατε να αυξήσετε την τιμή του vm.max_map_count σε {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "Μερικά παιχνίδια μπορεί να προσπαθήσουν να δημιουργήσουν περισσότερες αντιστοιχίσεις μνήμης από αυτές που επιτρέπονται τώρα. Ο Ryujinx θα καταρρεύσει μόλις ξεπεραστεί αυτό το όριο.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Ναι, μέχρι την επόμενη επανεκκίνηση", + "LinuxVmMaxMapCountDialogButtonPersistent": "Ναι, μόνιμα", + "LinuxVmMaxMapCountWarningTextPrimary": "Ο μέγιστος αριθμός αντιστοιχίσεων μνήμης είναι μικρότερος από τον συνιστώμενο.", + "LinuxVmMaxMapCountWarningTextSecondary": "Η τρέχουσα τιμή του vm.max_map_count ({0}) είναι χαμηλότερη από {1}. Ορισμένα παιχνίδια μπορεί να προσπαθήσουν να δημιουργήσουν περισσότερες αντιστοιχίσεις μνήμης από αυτές που επιτρέπονται τώρα. Ο Ryujinx θα συντριβεί μόλις ξεπεραστεί το όριο.\n\nΜπορεί να θέλετε είτε να αυξήσετε χειροκίνητα το όριο ή να εγκαταστήσετε το pkexec, το οποίο επιτρέπει Ryujinx να βοηθήσει με αυτό.", "Settings": "Ρυθμίσεις", "SettingsTabGeneral": "Εμφάνιση", "SettingsTabGeneralGeneral": "Γενικά", @@ -150,7 +155,7 @@ "SettingsTabGraphicsResolutionScaleNative": "Εγγενής (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Not recommended)", "SettingsTabGraphicsAspectRatio": "Αναλογία Απεικόνισης:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -228,10 +233,10 @@ "ControllerSettingsStickDown": "Κάτω", "ControllerSettingsStickLeft": "Αριστερά", "ControllerSettingsStickRight": "Δεξιά", - "ControllerSettingsStickStick": "Stick", - "ControllerSettingsStickInvertXAxis": "Invert Stick X", - "ControllerSettingsStickInvertYAxis": "Invert Stick Y", - "ControllerSettingsStickDeadzone": "Deadzone:", + "ControllerSettingsStickStick": "Μοχλός", + "ControllerSettingsStickInvertXAxis": "Αντιστροφή Μοχλού X", + "ControllerSettingsStickInvertYAxis": "Αντιστροφή Μοχλού Y", + "ControllerSettingsStickDeadzone": "Νεκρή Ζώνη:", "ControllerSettingsLStick": "Αριστερός Μοχλός", "ControllerSettingsRStick": "Δεξιός Μοχλός", "ControllerSettingsTriggersLeft": "Αριστερή Σκανδάλη", @@ -289,16 +294,12 @@ "ControllerSettingsSaveProfileToolTip": "Αποθήκευση Προφίλ", "MenuBarFileToolsTakeScreenshot": "Λήψη Στιγμιότυπου", "MenuBarFileToolsHideUi": "Απόκρυψη UI", - "GameListContextMenuRunApplication": "Run Application", + "GameListContextMenuRunApplication": "Εκτέλεση Εφαρμογής", "GameListContextMenuToggleFavorite": "Εναλλαγή Αγαπημένου", "GameListContextMenuToggleFavoriteToolTip": "Εναλλαγή της Κατάστασης Αγαπημένο του Παιχνιδιού", - "SettingsTabGeneralTheme": "Θέμα", - "SettingsTabGeneralThemeCustomTheme": "Προσαρμοσμένη Τοποθεσία Θέματος", - "SettingsTabGeneralThemeBaseStyle": "Βασικό Στυλ", - "SettingsTabGeneralThemeBaseStyleDark": "Σκούρο", - "SettingsTabGeneralThemeBaseStyleLight": "Ανοιχτό", - "SettingsTabGeneralThemeEnableCustomTheme": "Ενεργοποίηση Προσαρμοσμένου Θέματος", - "ButtonBrowse": "Αναζήτηση", + "SettingsTabGeneralTheme": "Theme:", + "SettingsTabGeneralThemeDark": "Dark", + "SettingsTabGeneralThemeLight": "Light", "ControllerSettingsConfigureGeneral": "Παραμέτρων", "ControllerSettingsRumble": "Δόνηση", "ControllerSettingsRumbleStrongMultiplier": "Ισχυρός Πολλαπλασιαστής Δόνησης", @@ -332,8 +333,6 @@ "DialogUpdaterAddingFilesMessage": "Προσθήκη Νέας Ενημέρωσης...", "DialogUpdaterCompleteMessage": "Η Ενημέρωση Ολοκληρώθηκε!", "DialogUpdaterRestartMessage": "Θέλετε να επανεκκινήσετε το Ryujinx τώρα;", - "DialogUpdaterArchNotSupportedMessage": "Δεν υπάρχει υποστηριζόμενη αρχιτεκτονική συστήματος!", - "DialogUpdaterArchNotSupportedSubMessage": "(Υποστηρίζονται μόνο συστήματα x64!)", "DialogUpdaterNoInternetMessage": "Δεν είστε συνδεδεμένοι στο Διαδίκτυο!", "DialogUpdaterNoInternetSubMessage": "Επαληθεύστε ότι έχετε σύνδεση στο Διαδίκτυο που λειτουργεί!", "DialogUpdaterDirtyBuildMessage": "Δεν μπορείτε να ενημερώσετε μία Πρόχειρη Έκδοση του Ryujinx!", @@ -385,7 +384,10 @@ "DialogUserProfileUnsavedChangesSubMessage": "Θέλετε να απορρίψετε τις αλλαγές σας;", "DialogControllerSettingsModifiedConfirmMessage": "Οι τρέχουσες ρυθμίσεις χειρισμού έχουν ενημερωθεί.", "DialogControllerSettingsModifiedConfirmSubMessage": "Θέλετε να αποθηκεύσετε;", - "DialogLoadNcaErrorMessage": "{0}. Σφάλμα Αρχείου: {1}", + "DialogLoadFileErrorMessage": "{0}. Errored File: {1}", + "DialogModAlreadyExistsMessage": "Mod already exists", + "DialogModInvalidMessage": "The specified directory does not contain a mod!", + "DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!", "DialogDlcNoDlcErrorMessage": "Το αρχείο δεν περιέχει DLC για τον επιλεγμένο τίτλο!", "DialogPerformanceCheckLoggingEnabledMessage": "Έχετε ενεργοποιημένη την καταγραφή εντοπισμού σφαλμάτων, η οποία έχει σχεδιαστεί για χρήση μόνο από προγραμματιστές.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Για βέλτιστη απόδοση, συνιστάται η απενεργοποίηση καταγραφής εντοπισμού σφαλμάτων. Θέλετε να απενεργοποιήσετε την καταγραφή τώρα;", @@ -396,6 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "Το αρχείο δεν περιέχει ενημέρωση για τον επιλεγμένο τίτλο!", "DialogSettingsBackendThreadingWarningTitle": "Προειδοποίηση - Backend Threading", "DialogSettingsBackendThreadingWarningMessage": "Το Ryujinx πρέπει να επανεκκινηθεί αφού αλλάξει αυτή η επιλογή για να εφαρμοστεί πλήρως. Ανάλογα με την πλατφόρμα σας, μπορεί να χρειαστεί να απενεργοποιήσετε με μη αυτόματο τρόπο το multithreading του ίδιου του προγράμματος οδήγησης όταν χρησιμοποιείτε το Ryujinx.", + "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", "SettingsTabGraphicsFeaturesOptions": "Χαρακτηριστικά", "SettingsTabGraphicsBackendMultithreading": "Πολυνηματική Επεξεργασία Γραφικών:", "CommonAuto": "Αυτόματο", @@ -430,6 +434,7 @@ "DlcManagerRemoveAllButton": "Αφαίρεση όλων", "DlcManagerEnableAllButton": "Ενεργοποίηση Όλων", "DlcManagerDisableAllButton": "Απενεργοποίηση Όλων", + "ModManagerDeleteAllButton": "Delete All", "MenuBarOptionsChangeLanguage": "Αλλαξε γλώσσα", "MenuBarShowFileTypes": "Εμφάνιση Τύπων Αρχείων", "CommonSort": "Κατάταξη", @@ -447,13 +452,13 @@ "CustomThemePathTooltip": "Διαδρομή προς το προσαρμοσμένο θέμα GUI", "CustomThemeBrowseTooltip": "Αναζητήστε ένα προσαρμοσμένο θέμα GUI", "DockModeToggleTooltip": "Ενεργοποιήστε ή απενεργοποιήστε τη λειτουργία σύνδεσης", - "DirectKeyboardTooltip": "Ενεργοποίηση ή απενεργοποίηση της \"υποστήριξης άμεσης πρόσβασης πληκτρολογίου (HID)\" (Παρέχει πρόσβαση στα παιχνίδια στο πληκτρολόγιό σας ως συσκευή εισαγωγής κειμένου)", - "DirectMouseTooltip": "Ενεργοποίηση ή απενεργοποίηση της \"υποστήριξης άμεσης πρόσβασης ποντικιού (HID)\" (Παρέχει πρόσβαση στα παιχνίδια στο ποντίκι σας ως συσκευή κατάδειξης)", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", "RegionTooltip": "Αλλαγή Περιοχής Συστήματος", "LanguageTooltip": "Αλλαγή Γλώσσας Συστήματος", "TimezoneTooltip": "Αλλαγή Ζώνης Ώρας Συστήματος", "TimeTooltip": "Αλλαγή Ώρας Συστήματος", - "VSyncToggleTooltip": "Ενεργοποιεί ή απενεργοποιεί τον κατακόρυφο συγχρονισμό", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", "PptcToggleTooltip": "Ενεργοποιεί ή απενεργοποιεί το PPTC", "FsIntegrityToggleTooltip": "Ενεργοποιεί τους ελέγχους ακεραιότητας σε αρχεία περιεχομένου παιχνιδιού", "AudioBackendTooltip": "Αλλαγή ήχου υποστήριξης", @@ -467,10 +472,10 @@ "GraphicsBackendThreadingTooltip": "Ενεργοποίηση Πολυνηματικής Επεξεργασίας Γραφικών", "GalThreadingTooltip": "Εκτελεί εντολές γραφικών σε ένα δεύτερο νήμα. Επιτρέπει την πολυνηματική μεταγλώττιση Shader σε χρόνο εκτέλεσης, μειώνει το τρεμόπαιγμα και βελτιώνει την απόδοση των προγραμμάτων οδήγησης χωρίς τη δική τους υποστήριξη πολλαπλών νημάτων. Ποικίλες κορυφαίες επιδόσεις σε προγράμματα οδήγησης με multithreading. Μπορεί να χρειαστεί επανεκκίνηση του Ryujinx για να απενεργοποιήσετε σωστά την ενσωματωμένη λειτουργία πολλαπλών νημάτων του προγράμματος οδήγησης ή ίσως χρειαστεί να το κάνετε χειροκίνητα για να έχετε την καλύτερη απόδοση.", "ShaderCacheToggleTooltip": "Ενεργοποιεί ή απενεργοποιεί την Προσωρινή Μνήμη Shader", - "ResolutionScaleTooltip": "Κλίμακα ανάλυσης που εφαρμόστηκε σε ισχύοντες στόχους απόδοσης", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", "ResolutionScaleEntryTooltip": "Κλίμακα ανάλυσης κινητής υποδιαστολής, όπως 1,5. Οι μη αναπόσπαστες τιμές είναι πιθανό να προκαλέσουν προβλήματα ή σφάλματα.", - "AnisotropyTooltip": "Επίπεδο Ανισότροπου Φιλτραρίσματος (ρυθμίστε το στο Αυτόματο για να χρησιμοποιηθεί η τιμή που ζητήθηκε από το παιχνίδι)", - "AspectRatioTooltip": "Λόγος διαστάσεων που εφαρμόστηκε στο παράθυρο απόδοσης.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", "ShaderDumpPathTooltip": "Τοποθεσία Εναπόθεσης Προσωρινής Μνήμης Shaders", "FileLogTooltip": "Ενεργοποιεί ή απενεργοποιεί την καταγραφή σε ένα αρχείο στο δίσκο", "StubLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων καταγραφής ατελειών", @@ -504,6 +509,8 @@ "EnableInternetAccessTooltip": "Επιτρέπει την πρόσβαση επισκέπτη στο Διαδίκτυο. Εάν ενεργοποιηθεί, η εξομοιωμένη κονσόλα Switch θα συμπεριφέρεται σαν να είναι συνδεδεμένη στο Διαδίκτυο. Λάβετε υπόψη ότι σε ορισμένες περιπτώσεις, οι εφαρμογές ενδέχεται να εξακολουθούν να έχουν πρόσβαση στο Διαδίκτυο, ακόμη και όταν αυτή η επιλογή είναι απενεργοποιημένη", "GameListContextMenuManageCheatToolTip": "Διαχείριση Κόλπων", "GameListContextMenuManageCheat": "Διαχείριση Κόλπων", + "GameListContextMenuManageModToolTip": "Manage Mods", + "GameListContextMenuManageMod": "Manage Mods", "ControllerSettingsStickRange": "Εύρος:", "DialogStopEmulationTitle": "Ryujinx - Διακοπή εξομοίωσης", "DialogStopEmulationMessage": "Είστε βέβαιοι ότι θέλετε να σταματήσετε την εξομοίωση;", @@ -515,8 +522,6 @@ "SettingsTabCpuMemory": "Μνήμη CPU", "DialogUpdaterFlatpakNotSupportedMessage": "Παρακαλούμε ενημερώστε το Ryujinx μέσω FlatHub.", "UpdaterDisabledWarningTitle": "Ο Διαχειριστής Ενημερώσεων Είναι Απενεργοποιημένος!", - "GameListContextMenuOpenSdModsDirectory": "Άνοιγμα Της Τοποθεσίας Των Atmosphere Mods", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Ανοίγει τον εναλλακτικό SD card Atmosphere κατάλογο που περιέχει Mods για το Application. Χρήσιμο για mods τα οποία συσκευάστηκαν για την πραγματική κονσόλα.", "ControllerSettingsRotate90": "Περιστροφή 90° Δεξιόστροφα", "IconSize": "Μέγεθος Εικονιδίου", "IconSizeTooltip": "Αλλάξτε μέγεθος εικονιδίων των παιχνιδιών", @@ -544,12 +549,13 @@ "SwkbdMinCharacters": "Πρέπει να έχει μήκος τουλάχιστον {0} χαρακτήρες", "SwkbdMinRangeCharacters": "Πρέπει να έχει μήκος {0}-{1} χαρακτήρες", "SoftwareKeyboard": "Εικονικό Πληκτρολόγιο", - "SoftwareKeyboardModeNumbersOnly": "Must be numbers only", - "SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only", - "SoftwareKeyboardModeASCII": "Must be ASCII text only", - "DialogControllerAppletMessagePlayerRange": "Η εφαρμογή ζητά {0} παίκτη(ες) με:\n\nΤΥΠΟΥΣ: {1}\n\nΠΑΙΚΤΕΣ: {2}\n\n{3}Παρακαλώ ανοίξτε τις ρυθμίσεις και αλλάξτε τις ρυθμίσεις εισαγωγής τώρα ή πατήστε Κλείσιμο.", - "DialogControllerAppletMessage": "Η εφαρμογή ζητά ακριβώς {0} παίκτη(ες) με:\n\nΤΥΠΟΥΣ: {1}\n\nΠΑΙΚΤΕΣ: {2}\n\n{3}Παρακαλώ ανοίξτε τις ρυθμίσεις και αλλάξτε τις Ρυθμίσεις εισαγωγής τώρα ή πατήστε Κλείσιμο.", - "DialogControllerAppletDockModeSet": "Η κατάσταση Docked ενεργοποιήθηκε. Το συσκευή παλάμης είναι επίσης μην αποδεκτό.", + "SoftwareKeyboardModeNumeric": "Πρέπει να είναι 0-9 ή '.' μόνο", + "SoftwareKeyboardModeAlphabet": "Πρέπει να μην είναι μόνο χαρακτήρες CJK", + "SoftwareKeyboardModeASCII": "Πρέπει να είναι μόνο κείμενο ASCII", + "ControllerAppletControllers": "Supported Controllers:", + "ControllerAppletPlayers": "Players:", + "ControllerAppletDescription": "Your current configuration is invalid. Open settings and reconfigure your inputs.", + "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.", "UpdaterRenaming": "Μετονομασία Παλαιών Αρχείων...", "UpdaterRenameFailed": "Δεν ήταν δυνατή η μετονομασία του αρχείου: {0}", "UpdaterAddingFiles": "Προσθήκη Νέων Αρχείων...", @@ -587,6 +593,7 @@ "Writable": "Εγγράψιμο", "SelectDlcDialogTitle": "Επιλογή αρχείων DLC", "SelectUpdateDialogTitle": "Επιλογή αρχείων ενημέρωσης", + "SelectModDialogTitle": "Select mod directory", "UserProfileWindowTitle": "Διαχειριστής Προφίλ Χρήστη", "CheatWindowTitle": "Διαχειριστής των Cheats", "DlcWindowTitle": "Downloadable Content Manager", @@ -594,10 +601,12 @@ "CheatWindowHeading": "Διαθέσιμα Cheats για {0} [{1}]", "BuildId": "BuildId:", "DlcWindowHeading": "{0} Downloadable Content(s) available for {1} ({2})", + "ModWindowHeading": "{0} Mod(s)", "UserProfilesEditProfile": "Επεξεργασία Επιλεγμένων", "Cancel": "Ακύρωση", "Save": "Αποθήκευση", "Discard": "Απόρριψη", + "Paused": "Σε παύση", "UserProfilesSetProfileImage": "Ορισμός Εικόνας Προφίλ", "UserProfileEmptyNameError": "Απαιτείται όνομα", "UserProfileNoImageError": "Η εικόνα προφίλ πρέπει να οριστεί", @@ -607,9 +616,9 @@ "UserProfilesName": "Όνομα:", "UserProfilesUserId": "User Id:", "SettingsTabGraphicsBackend": "Σύστημα Υποστήριξης Γραφικών", - "SettingsTabGraphicsBackendTooltip": "Backend Γραφικών που θα χρησιμοποιηθεί", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", "SettingsEnableTextureRecompression": "Ενεργοποίηση Επανασυμπίεσης Των Texture", - "SettingsEnableTextureRecompressionTooltip": "Συμπιέζει συγκεκριμένα texture για να μειωθεί η χρήση της VRAM.\nΣυνίσταται για χρήση σε κάρτες γραφικών με λιγότερο από 4 GiB VRAM.\nΑφήστε το Απενεργοποιημένο αν είστε αβέβαιοι.", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", "SettingsTabGraphicsPreferredGpu": "Προτιμώμενη GPU", "SettingsTabGraphicsPreferredGpuTooltip": "Επιλέξτε την κάρτα γραφικών η οποία θα χρησιμοποιηθεί από το Vulkan.\n\nΔεν επηρεάζει το OpenGL.\n\nΔιαλέξτε την GPU που διαθέτει την υπόδειξη \"dGPU\" αν δεν είστε βέβαιοι. Αν δεν υπάρχει κάποιαν, το πειράξετε", "SettingsAppRequiredRestartMessage": "Απαιτείται Επανεκκίνηση Του Ryujinx", @@ -620,8 +629,8 @@ "SettingsTabHotkeysVolumeDownHotkey": "Μείωση Έντασης:", "SettingsEnableMacroHLE": "Ενεργοποίηση του Macro HLE", "SettingsEnableMacroHLETooltip": "Προσομοίωση του κώδικα GPU Macro .\n\nΒελτιώνει την απόδοση, αλλά μπορεί να προκαλέσει γραφικά προβλήματα σε μερικά παιχνίδια.\n\nΑφήστε ΕΝΕΡΓΟ αν δεν είστε σίγουροι.", - "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", - "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", + "SettingsEnableColorSpacePassthrough": "Διέλευση Χρωματικού Χώρου", + "SettingsEnableColorSpacePassthroughTooltip": "Σκηνοθετεί το σύστημα υποστήριξης του Vulkan για να περάσει από πληροφορίες χρώματος χωρίς να καθορίσει έναν χρωματικό χώρο. Για χρήστες με ευρείες οθόνες γκάμας, αυτό μπορεί να οδηγήσει σε πιο ζωηρά χρώματα, με κόστος την ορθότητα του χρώματος.", "VolumeShort": "Έντ.", "UserProfilesManageSaves": "Διαχείριση Των Save", "DeleteUserSave": "Επιθυμείτε να διαγράψετε το save χρήστη για το συγκεκριμένο παιχνίδι;", @@ -635,12 +644,12 @@ "Recover": "Ανάκτηση", "UserProfilesRecoverHeading": "Βρέθηκαν save για τους ακόλουθους λογαριασμούς", "UserProfilesRecoverEmptyList": "Δεν υπάρχουν προφίλ για ανάκτηση", - "GraphicsAATooltip": "Εφαρμόζει anti-aliasing στην απόδοση του παιχνιδιού", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", "GraphicsAALabel": "Anti-Aliasing", "GraphicsScalingFilterLabel": "Φίλτρο Κλιμάκωσης:", - "GraphicsScalingFilterTooltip": "Ενεργοποιεί Κλίμακα Framebuffer", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", "GraphicsScalingFilterLevelLabel": "Επίπεδο", - "GraphicsScalingFilterLevelTooltip": "Ορισμός Επιπέδου Φίλτρου Κλιμάκωσης", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "Χαμηλό SMAA", "SmaaMedium": " Μεσαίο SMAA", "SmaaHigh": "Υψηλό SMAA", @@ -648,9 +657,12 @@ "UserEditorTitle": "Επεξεργασία Χρήστη", "UserEditorTitleCreate": "Δημιουργία Χρήστη", "SettingsTabNetworkInterface": "Διεπαφή Δικτύου", - "NetworkInterfaceTooltip": "Η διεπαφή δικτύου που χρησιμοποιείται για τα χαρακτηριστικά LAN", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", "NetworkInterfaceDefault": "Προεπιλογή", "PackagingShaders": "Shaders Συσκευασίας", - "AboutChangelogButton": "View Changelog on GitHub", - "AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser." -} \ No newline at end of file + "AboutChangelogButton": "Προβολή αρχείου αλλαγών στο GitHub", + "AboutChangelogButtonTooltipMessage": "Κάντε κλικ για να ανοίξετε το αρχείο αλλαγών για αυτήν την έκδοση στο προεπιλεγμένο πρόγραμμα περιήγησης σας.", + "SettingsTabNetworkMultiplayer": "Πολλαπλοί παίκτες", + "MultiplayerMode": "Λειτουργία:", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." +} diff --git a/src/Ryujinx/Assets/Locales/es_ES.json b/src/Ryujinx/Assets/Locales/es_ES.json index 91bcd8f114..ff41c855f1 100644 --- a/src/Ryujinx/Assets/Locales/es_ES.json +++ b/src/Ryujinx/Assets/Locales/es_ES.json @@ -30,7 +30,7 @@ "MenuBarToolsManageFileTypes": "Administrar tipos de archivo", "MenuBarToolsInstallFileTypes": "Instalar tipos de archivo", "MenuBarToolsUninstallFileTypes": "Desinstalar tipos de archivo", - "MenuBarHelp": "Ayuda", + "MenuBarHelp": "_Ayuda", "MenuBarHelpCheckForUpdates": "Buscar actualizaciones", "MenuBarHelpAbout": "Acerca de", "MenuSearch": "Buscar...", @@ -54,17 +54,15 @@ "GameListContextMenuManageTitleUpdatesToolTip": "Abrir la ventana de gestión de actualizaciones de esta aplicación", "GameListContextMenuManageDlc": "Gestionar DLC", "GameListContextMenuManageDlcToolTip": "Abrir la ventana de gestión del DLC", - "GameListContextMenuOpenModsDirectory": "Abrir carpeta de mods", - "GameListContextMenuOpenModsDirectoryToolTip": "Abrir la carpeta que contiene los mods (archivos modificantes) de esta aplicación", "GameListContextMenuCacheManagement": "Gestión de caché ", "GameListContextMenuCacheManagementPurgePptc": "Reconstruir PPTC en cola", "GameListContextMenuCacheManagementPurgePptcToolTip": "Elimina la caché de PPTC de esta aplicación", - "GameListContextMenuCacheManagementPurgeShaderCache": "Limpiar caché de sombras", - "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Eliminar la caché de sombras de esta aplicación", + "GameListContextMenuCacheManagementPurgeShaderCache": "Limpiar caché de sombreadores", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Eliminar la caché de sombreadores de esta aplicación", "GameListContextMenuCacheManagementOpenPptcDirectory": "Abrir carpeta de PPTC", "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Abrir la carpeta que contiene la caché de PPTC de esta aplicación", - "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Abrir carpeta de caché de sombras", - "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Abrir la carpeta que contiene la caché de sombras de esta aplicación", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Abrir carpeta de caché de sombreadores", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Abrir la carpeta que contiene la caché de sombreadores de esta aplicación", "GameListContextMenuExtractData": "Extraer datos", "GameListContextMenuExtractDataExeFS": "ExeFS", "GameListContextMenuExtractDataExeFSToolTip": "Extraer la sección ExeFS de la configuración actual de la aplicación (incluyendo actualizaciones)", @@ -72,6 +70,13 @@ "GameListContextMenuExtractDataRomFSToolTip": "Extraer la sección RomFS de la configuración actual de la aplicación (incluyendo actualizaciones)", "GameListContextMenuExtractDataLogo": "Logotipo", "GameListContextMenuExtractDataLogoToolTip": "Extraer la sección Logo de la configuración actual de la aplicación (incluyendo actualizaciones)", + "GameListContextMenuCreateShortcut": "Crear acceso directo de aplicación", + "GameListContextMenuCreateShortcutToolTip": "Crear un acceso directo en el escritorio que lance la aplicación seleccionada", + "GameListContextMenuCreateShortcutToolTipMacOS": "Crea un acceso directo en la carpeta de Aplicaciones de macOS que inicie la Aplicación seleccionada", + "GameListContextMenuOpenModsDirectory": "Abrir Directorio de Mods", + "GameListContextMenuOpenModsDirectoryToolTip": "Abre el directorio que contiene los Mods de la Aplicación.", + "GameListContextMenuOpenSdModsDirectory": "Abrir Directorio de Mods de Atmosphere\n\n\n\n\n\n", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Abre el directorio alternativo de la tarjeta SD de Atmosphere que contiene los Mods de la Aplicación. Útil para los mods que están empaquetados para el hardware real.", "StatusBarGamesLoaded": "{0}/{1} juegos cargados", "StatusBarSystemVersion": "Versión del sistema: {0}", "LinuxVmMaxMapCountDialogTitle": "Límite inferior para mapeos de memoria detectado", @@ -138,7 +143,7 @@ "SettingsTabSystemIgnoreMissingServices": "Ignorar servicios no implementados", "SettingsTabGraphics": "Gráficos", "SettingsTabGraphicsAPI": "API de gráficos", - "SettingsTabGraphicsEnableShaderCache": "Habilitar caché de sombras", + "SettingsTabGraphicsEnableShaderCache": "Habilitar caché de sombreadores", "SettingsTabGraphicsAnisotropicFiltering": "Filtro anisotrópico:", "SettingsTabGraphicsAnisotropicFilteringAuto": "Automático", "SettingsTabGraphicsAnisotropicFiltering2x": "x2", @@ -150,7 +155,7 @@ "SettingsTabGraphicsResolutionScaleNative": "Nativa (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "x2 (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "x3 (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "x4 (2880p/4320p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Not recommended)", "SettingsTabGraphicsAspectRatio": "Relación de aspecto:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -159,7 +164,7 @@ "SettingsTabGraphicsAspectRatio32x9": "32:9", "SettingsTabGraphicsAspectRatioStretch": "Estirar a la ventana", "SettingsTabGraphicsDeveloperOptions": "Opciones de desarrollador", - "SettingsTabGraphicsShaderDumpPath": "Directorio de volcado de sombras:", + "SettingsTabGraphicsShaderDumpPath": "Directorio de volcado de sombreadores:", "SettingsTabLogging": "Registros", "SettingsTabLoggingLogging": "Registros", "SettingsTabLoggingEnableLoggingToFile": "Habilitar registro a archivo", @@ -292,13 +297,9 @@ "GameListContextMenuRunApplication": "Ejecutar aplicación", "GameListContextMenuToggleFavorite": "Marcar favorito", "GameListContextMenuToggleFavoriteToolTip": "Marca o desmarca el juego como favorito", - "SettingsTabGeneralTheme": "Tema", - "SettingsTabGeneralThemeCustomTheme": "Directorio de tema personalizado", - "SettingsTabGeneralThemeBaseStyle": "Estilo base", - "SettingsTabGeneralThemeBaseStyleDark": "Oscuro", - "SettingsTabGeneralThemeBaseStyleLight": "Claro", - "SettingsTabGeneralThemeEnableCustomTheme": "Habilitar tema personalizado", - "ButtonBrowse": "Buscar", + "SettingsTabGeneralTheme": "Tema:", + "SettingsTabGeneralThemeDark": "Oscuro", + "SettingsTabGeneralThemeLight": "Claro", "ControllerSettingsConfigureGeneral": "Configurar", "ControllerSettingsRumble": "Vibración", "ControllerSettingsRumbleStrongMultiplier": "Multiplicador de vibraciones fuertes", @@ -332,8 +333,6 @@ "DialogUpdaterAddingFilesMessage": "Aplicando actualización...", "DialogUpdaterCompleteMessage": "¡Actualización completa!", "DialogUpdaterRestartMessage": "¿Quieres reiniciar Ryujinx?", - "DialogUpdaterArchNotSupportedMessage": "¡Tu arquitectura de sistema no es compatible!", - "DialogUpdaterArchNotSupportedSubMessage": "(¡Solo son compatibles los sistemas x64!)", "DialogUpdaterNoInternetMessage": "¡No estás conectado a internet!", "DialogUpdaterNoInternetSubMessage": "¡Por favor, verifica que tu conexión a Internet funciona!", "DialogUpdaterDirtyBuildMessage": "¡No puedes actualizar una versión \"dirty\" de Ryujinx!", @@ -385,17 +384,22 @@ "DialogUserProfileUnsavedChangesSubMessage": "¿Quieres descartar los cambios realizados?", "DialogControllerSettingsModifiedConfirmMessage": "Se ha actualizado la configuración del mando actual.", "DialogControllerSettingsModifiedConfirmSubMessage": "¿Guardar cambios?", - "DialogLoadNcaErrorMessage": "{0}. Archivo con error: {1}", + "DialogLoadFileErrorMessage": "{0}. Archivo con error: {1}", + "DialogModAlreadyExistsMessage": "El mod ya existe", + "DialogModInvalidMessage": "¡El directorio especificado no contiene un mod!", + "DialogModDeleteNoParentMessage": "Error al eliminar: ¡No se pudo encontrar el directorio principal para el mod \"{0}\"!", "DialogDlcNoDlcErrorMessage": "¡Ese archivo no contiene contenido descargable para el título seleccionado!", "DialogPerformanceCheckLoggingEnabledMessage": "Has habilitado los registros debug, diseñados solo para uso de los desarrolladores.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Para un rendimiento óptimo, se recomienda deshabilitar los registros debug. ¿Quieres deshabilitarlos ahora?", - "DialogPerformanceCheckShaderDumpEnabledMessage": "Has habilitado el volcado de sombras, diseñado solo para uso de los desarrolladores.", - "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Para un rendimiento óptimo, se recomienda deshabilitar el volcado de sombraa. ¿Quieres deshabilitarlo ahora?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Has habilitado el volcado de sombreadores, diseñado solo para uso de los desarrolladores.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Para un rendimiento óptimo, se recomienda deshabilitar el volcado de sombreadores. ¿Quieres deshabilitarlo ahora?", "DialogLoadAppGameAlreadyLoadedMessage": "Ya has cargado un juego", "DialogLoadAppGameAlreadyLoadedSubMessage": "Por favor, detén la emulación o cierra el emulador antes de iniciar otro juego.", "DialogUpdateAddUpdateErrorMessage": "¡Ese archivo no contiene una actualización para el título seleccionado!", "DialogSettingsBackendThreadingWarningTitle": "Advertencia - multihilado de gráficos", "DialogSettingsBackendThreadingWarningMessage": "Ryujinx debe reiniciarse para aplicar este cambio. Dependiendo de tu plataforma, puede que tengas que desactivar manualmente la optimización enlazada de tus controladores gráficos para usar el multihilo de Ryujinx.", + "DialogModManagerDeletionWarningMessage": "Estás a punto de eliminar el mod: {0}\n\n¿Estás seguro de que quieres continuar?", + "DialogModManagerDeletionAllWarningMessage": "Estás a punto de eliminar todos los Mods para este título.\n\n¿Estás seguro de que quieres continuar?", "SettingsTabGraphicsFeaturesOptions": "Funcionalidades", "SettingsTabGraphicsBackendMultithreading": "Multihilado del motor gráfico:", "CommonAuto": "Automático", @@ -430,6 +434,7 @@ "DlcManagerRemoveAllButton": "Quitar todo", "DlcManagerEnableAllButton": "Activar todas", "DlcManagerDisableAllButton": "Desactivar todos", + "ModManagerDeleteAllButton": "Eliminar Todo", "MenuBarOptionsChangeLanguage": "Cambiar idioma", "MenuBarShowFileTypes": "Mostrar tipos de archivo", "CommonSort": "Orden", @@ -447,13 +452,13 @@ "CustomThemePathTooltip": "Carpeta que contiene los temas personalizados para la interfaz", "CustomThemeBrowseTooltip": "Busca un tema personalizado para la interfaz", "DockModeToggleTooltip": "El modo dock o modo TV hace que la consola emulada se comporte como una Nintendo Switch en su dock. Esto mejora la calidad gráfica en la mayoría de los juegos. Del mismo modo, si lo desactivas, el sistema emulado se comportará como una Nintendo Switch en modo portátil, reduciendo la cálidad de los gráficos.\n\nConfigura los controles de \"Jugador\" 1 si planeas jugar en modo dock/TV; configura los controles de \"Portátil\" si planeas jugar en modo portátil.\n\nActívalo si no sabes qué hacer.", - "DirectKeyboardTooltip": "Activa o desactiva \"soporte para acceso directo al teclado (HID)\" (Permite a los juegos utilizar tu teclado como entrada de texto)", - "DirectMouseTooltip": "Activa o desactiva \"soporte para acceso directo al ratón (HID)\" (Permite a los juegos utilizar tu ratón como cursor)", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", "RegionTooltip": "Cambia la región del sistema", "LanguageTooltip": "Cambia el idioma del sistema", "TimezoneTooltip": "Cambia la zona horaria del sistema", "TimeTooltip": "Cambia la hora del sistema", - "VSyncToggleTooltip": "Sincronización vertical del sistema emulado. A efectos prácticos es un límite de fotogramas para la mayoría de juegos; deshabilitarlo puede hacer que los juegos se aceleren o que las pantallas de carga tarden más o se atasquen.\n\nPuedes activar y desactivar esto mientras el emulador está funcionando con un atajo de teclado a tu elección. Recomendamos hacer esto en vez de deshabilitarlo.\n\nActívalo si no sabes qué hacer.", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", "PptcToggleTooltip": "Guarda funciones de JIT traducidas para que no sea necesario traducirlas cada vez que el juego carga.\n\nReduce los tirones y acelera significativamente el tiempo de inicio de los juegos después de haberlos ejecutado al menos una vez.\n\nActívalo si no sabes qué hacer.", "FsIntegrityToggleTooltip": "Comprueba si hay archivos corruptos en los juegos que ejecutes al abrirlos, y si detecta archivos corruptos, muestra un error de Hash en los registros.\n\nEsto no tiene impacto alguno en el rendimiento y está pensado para ayudar a resolver problemas.\n\nActívalo si no sabes qué hacer.", "AudioBackendTooltip": "Cambia el motor usado para renderizar audio.\n\nSDL2 es el preferido, mientras que OpenAL y SoundIO se usan si hay problemas con este. Dummy no produce audio.\n\nSelecciona SDL2 si no sabes qué hacer.", @@ -464,13 +469,13 @@ "UseHypervisorTooltip": "Usar Hypervisor en lugar de JIT. Mejora enormemente el rendimiento cuando está disponible, pero puede ser inestable en su estado actual.", "DRamTooltip": "Expande la memoria DRAM del sistema emulado de 4GiB a 6GiB.\n\nUtilizar solo con packs de texturas HD o mods de resolución 4K. NO mejora el rendimiento.\n\nDesactívalo si no sabes qué hacer.", "IgnoreMissingServicesTooltip": "Hack para ignorar servicios no implementados del Horizon OS. Esto puede ayudar a sobrepasar crasheos cuando inicies ciertos juegos.\n\nDesactívalo si no sabes qué hacer.", - "GraphicsBackendThreadingTooltip": "Ejecuta los comandos del motor gráfico en un segundo hilo. Acelera la compilación de sombreadores, reduce los tirones, y mejora el rendimiento en controladores gráficos que no realicen su propio multihilado. Rendimiento máximo ligeramente superior en controladores gráficos que soporten multihilado.\n\nSelecciona \"Auto\" si no sabes qué hacer.", - "GalThreadingTooltip": "Ejecuta los comandos del motor gráfico en un segundo hilo. Acelera la compilación de sombreadores, reduce los tirones, y mejora el rendimiento en controladores gráficos que no realicen su propio multihilado. Rendimiento máximo ligeramente superior en controladores gráficos que soporten multihilado.\n\nSelecciona \"Auto\" si no sabes qué hacer.", + "GraphicsBackendThreadingTooltip": "Ejecuta los comandos del motor gráfico en un segundo hilo. Acelera la compilación de sombreadores, reduce los tirones, y mejora el rendimiento en controladores gráficos que no realicen su propio procesamiento con múltiples hilos. Rendimiento ligeramente superior en controladores gráficos que soporten múltiples hilos.\n\nSelecciona \"Auto\" si no sabes qué hacer.", + "GalThreadingTooltip": "Ejecuta los comandos del motor gráfico en un segundo hilo. Acelera la compilación de sombreadores, reduce los tirones, y mejora el rendimiento en controladores gráficos que no realicen su propio procesamiento con múltiples hilos. Rendimiento ligeramente superior en controladores gráficos que soporten múltiples hilos.\n\nSelecciona \"Auto\" si no sabes qué hacer.", "ShaderCacheToggleTooltip": "Guarda una caché de sombreadores en disco, la cual reduce los tirones a medida que vas jugando.\n\nActívalo si no sabes qué hacer.", - "ResolutionScaleTooltip": "Escala de resolución aplicada a objetivos aplicables en el renderizado", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", "ResolutionScaleEntryTooltip": "Escalado de resolución de coma flotante, como por ejemplo 1,5. Los valores no íntegros pueden causar errores gráficos o crashes.", - "AnisotropyTooltip": "Nivel de filtrado anisotrópico (selecciona Auto para utilizar el valor solicitado por el juego)", - "AspectRatioTooltip": "Relación de aspecto aplicada a la ventana de renderizado.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", "ShaderDumpPathTooltip": "Directorio en el cual se volcarán los sombreadores de los gráficos", "FileLogTooltip": "Guarda los registros de la consola en archivos en disco. No afectan al rendimiento.", "StubLogTooltip": "Escribe mensajes de Stub en la consola. No afectan al rendimiento.", @@ -504,6 +509,8 @@ "EnableInternetAccessTooltip": "Permite a la aplicación emulada conectarse a Internet.\n\nLos juegos que tengan modo LAN podrán conectarse entre sí habilitando esta opción y estando conectados al mismo módem. Asimismo, esto permite conexiones con consolas reales.\n\nNO permite conectar con los servidores de Nintendo Online. Puede causar que ciertos juegos crasheen al intentar conectarse a sus servidores.\n\nDesactívalo si no estás seguro.", "GameListContextMenuManageCheatToolTip": "Activa o desactiva los cheats", "GameListContextMenuManageCheat": "Administrar cheats", + "GameListContextMenuManageModToolTip": "Gestionar Mods", + "GameListContextMenuManageMod": "Gestionar Mods", "ControllerSettingsStickRange": "Alcance:", "DialogStopEmulationTitle": "Ryujinx - Detener emulación", "DialogStopEmulationMessage": "¿Seguro que quieres detener la emulación actual?", @@ -515,13 +522,11 @@ "SettingsTabCpuMemory": "Memoria de CPU", "DialogUpdaterFlatpakNotSupportedMessage": "Por favor, actualiza Ryujinx a través de FlatHub.", "UpdaterDisabledWarningTitle": "¡Actualizador deshabilitado!", - "GameListContextMenuOpenSdModsDirectory": "Abrir carpeta de mods Atmosphere", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Abre la carpeta alternativa de mods en la que puedes insertar mods de Atmosphere. Útil para mods que vengan organizados para uso en consola.", "ControllerSettingsRotate90": "Rotar 90° en el sentido de las agujas del reloj", "IconSize": "Tamaño de iconos", "IconSizeTooltip": "Cambia el tamaño de los iconos de juegos", "MenuBarOptionsShowConsole": "Mostrar consola", - "ShaderCachePurgeError": "Error al eliminar la caché en {0}: {1}", + "ShaderCachePurgeError": "Error al eliminar la caché de sombreadores en {0}: {1}", "UserErrorNoKeys": "No se encontraron keys", "UserErrorNoFirmware": "No se encontró firmware", "UserErrorFirmwareParsingFailed": "Error al analizar el firmware", @@ -544,12 +549,13 @@ "SwkbdMinCharacters": "Debe tener al menos {0} caracteres", "SwkbdMinRangeCharacters": "Debe tener {0}-{1} caracteres", "SoftwareKeyboard": "Teclado de software", - "SoftwareKeyboardModeNumbersOnly": "Solo deben ser números", + "SoftwareKeyboardModeNumeric": "Debe ser sólo 0-9 o '.'", "SoftwareKeyboardModeAlphabet": "Solo deben ser caracteres no CJK", "SoftwareKeyboardModeASCII": "Solo deben ser texto ASCII", - "DialogControllerAppletMessagePlayerRange": "La aplicación require {0} jugador(es) con:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Por favor abre las opciones y reconfigura los dispositivos de entrada o presiona 'Cerrar'.", - "DialogControllerAppletMessage": "La aplicación require exactamente {0} jugador(es) con:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Por favor abre las opciones y reconfigura los dispositivos de entrada o presiona 'Cerrar'.", - "DialogControllerAppletDockModeSet": "Modo dock/TV activo. El control portátil también es inválido.\n\n", + "ControllerAppletControllers": "Controladores Compatibles:", + "ControllerAppletPlayers": "Jugadores:", + "ControllerAppletDescription": "Tu configuración actual no es válida. Abre la configuración y vuelve a configurar tus entradas", + "ControllerAppletDocked": "Modo acoplado activado. El modo portátil debería estar desactivado.", "UpdaterRenaming": "Renombrando archivos viejos...", "UpdaterRenameFailed": "El actualizador no pudo renombrar el archivo: {0}", "UpdaterAddingFiles": "Añadiendo nuevos archivos...", @@ -587,6 +593,7 @@ "Writable": "Escribible", "SelectDlcDialogTitle": "Selecciona archivo(s) de DLC", "SelectUpdateDialogTitle": "Selecciona archivo(s) de actualización", + "SelectModDialogTitle": "Seleccionar un directorio de Mods", "UserProfileWindowTitle": "Administrar perfiles de usuario", "CheatWindowTitle": "Administrar cheats", "DlcWindowTitle": "Administrar contenido descargable", @@ -594,10 +601,12 @@ "CheatWindowHeading": "Cheats disponibles para {0} [{1}]", "BuildId": "Id de compilación:", "DlcWindowHeading": "Contenido descargable disponible para {0} [{1}]", + "ModWindowHeading": "{0} Mod(s)", "UserProfilesEditProfile": "Editar selección", "Cancel": "Cancelar", "Save": "Guardar", "Discard": "Descartar", + "Paused": "Pausado", "UserProfilesSetProfileImage": "Elegir Imagen de Perfil ", "UserProfileEmptyNameError": "El nombre es obligatorio", "UserProfileNoImageError": "Debe establecerse la imagen de perfil", @@ -607,9 +616,9 @@ "UserProfilesName": "Nombre:", "UserProfilesUserId": "Id de Usuario:", "SettingsTabGraphicsBackend": "Fondo de gráficos", - "SettingsTabGraphicsBackendTooltip": "Back-end de los gráficos a utilizar", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", "SettingsEnableTextureRecompression": "Activar recompresión de texturas", - "SettingsEnableTextureRecompressionTooltip": "Comprime ciertas texturas para reducir el uso de la VRAM.\n\nRecomendado para GPUs que tienen menos de 4 GB de VRAM.\n\nMantén esta opción desactivada si no estás seguro.", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", "SettingsTabGraphicsPreferredGpu": "GPU preferida", "SettingsTabGraphicsPreferredGpuTooltip": "Selecciona la tarjeta gráfica que se utilizará con los back-end de gráficos Vulkan.\n\nNo afecta la GPU que utilizará OpenGL.\n\nFije a la GPU marcada como \"dGUP\" ante dudas. Si no hay una, no haga modificaciones.", "SettingsAppRequiredRestartMessage": "Reinicio de Ryujinx requerido.", @@ -635,12 +644,12 @@ "Recover": "Recuperar", "UserProfilesRecoverHeading": "Datos de guardado fueron encontrados para las siguientes cuentas", "UserProfilesRecoverEmptyList": "No hay perfiles a recuperar", - "GraphicsAATooltip": "Aplica el suavizado de bordes al procesamiento del juego", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", "GraphicsAALabel": "Suavizado de bordes:", "GraphicsScalingFilterLabel": "Filtro de escalado:", - "GraphicsScalingFilterTooltip": "Activa el escalado de búfer de fotogramas", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", "GraphicsScalingFilterLevelLabel": "Nivel", - "GraphicsScalingFilterLevelTooltip": "Establecer nivel del filtro de escalado", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "SMAA Bajo", "SmaaMedium": "SMAA Medio", "SmaaHigh": "SMAA Alto", @@ -648,9 +657,12 @@ "UserEditorTitle": "Editar usuario", "UserEditorTitleCreate": "Crear Usuario", "SettingsTabNetworkInterface": "Interfaz de Red", - "NetworkInterfaceTooltip": "Interfaz de red usada para las características LAN", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", "NetworkInterfaceDefault": "Predeterminado", "PackagingShaders": "Empaquetando sombreadores", "AboutChangelogButton": "Ver registro de cambios en GitHub", - "AboutChangelogButtonTooltipMessage": "Haga clic para abrir el registro de cambios para esta versión en su navegador predeterminado." -} \ No newline at end of file + "AboutChangelogButtonTooltipMessage": "Haga clic para abrir el registro de cambios para esta versión en su navegador predeterminado.", + "SettingsTabNetworkMultiplayer": "Multijugador", + "MultiplayerMode": "Modo:", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." +} diff --git a/src/Ryujinx/Assets/Locales/fr_FR.json b/src/Ryujinx/Assets/Locales/fr_FR.json index 5bab6f7b2c..1c84573c27 100644 --- a/src/Ryujinx/Assets/Locales/fr_FR.json +++ b/src/Ryujinx/Assets/Locales/fr_FR.json @@ -1,7 +1,7 @@ { "Language": "Français", - "MenuBarFileOpenApplet": "Ouvrir Applet", - "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Ouvrir l'Éditeur Applet Mii en mode autonome", + "MenuBarFileOpenApplet": "Ouvrir un applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Ouvrir l'Applet Mii Editor en mode Standalone", "SettingsTabInputDirectMouseAccess": "Accès direct à la souris", "SettingsTabSystemMemoryManagerMode": "Mode de gestion de la mémoire :", "SettingsTabSystemMemoryManagerModeSoftware": "Logiciel", @@ -14,9 +14,9 @@ "MenuBarFileOpenEmuFolder": "Ouvrir le dossier Ryujinx", "MenuBarFileOpenLogsFolder": "Ouvrir le dossier des journaux", "MenuBarFileExit": "_Quitter", - "MenuBarOptions": "Options", + "MenuBarOptions": "_Options", "MenuBarOptionsToggleFullscreen": "Basculer en plein écran", - "MenuBarOptionsStartGamesInFullscreen": "Démarrer jeux en plein écran", + "MenuBarOptionsStartGamesInFullscreen": "Démarrer le jeu en plein écran", "MenuBarOptionsStopEmulation": "Arrêter l'émulation", "MenuBarOptionsSettings": "_Paramètres", "MenuBarOptionsManageUserProfiles": "_Gérer les profils d'utilisateurs", @@ -30,9 +30,9 @@ "MenuBarToolsManageFileTypes": "Gérer les types de fichiers", "MenuBarToolsInstallFileTypes": "Installer les types de fichiers", "MenuBarToolsUninstallFileTypes": "Désinstaller les types de fichiers", - "MenuBarHelp": "Aide", + "MenuBarHelp": "_Aide", "MenuBarHelpCheckForUpdates": "Vérifier les mises à jour", - "MenuBarHelpAbout": "Á propos", + "MenuBarHelpAbout": "À propos", "MenuSearch": "Rechercher...", "GameListHeaderFavorite": "Favoris", "GameListHeaderIcon": "Icône", @@ -40,8 +40,8 @@ "GameListHeaderDeveloper": "Développeur", "GameListHeaderVersion": "Version", "GameListHeaderTimePlayed": "Temps de jeu", - "GameListHeaderLastPlayed": "jouer la dernière fois", - "GameListHeaderFileExtension": "Fichier externe", + "GameListHeaderLastPlayed": "Dernière partie jouée", + "GameListHeaderFileExtension": "Extension du Fichier", "GameListHeaderFileSize": "Taille du Fichier", "GameListHeaderPath": "Chemin", "GameListContextMenuOpenUserSaveDirectory": "Ouvrir le dossier de sauvegarde utilisateur", @@ -51,15 +51,13 @@ "GameListContextMenuOpenBcatSaveDirectory": "Ouvrir le dossier de sauvegarde BCAT", "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Ouvre le dossier contenant la sauvegarde BCAT du jeu", "GameListContextMenuManageTitleUpdates": "Gérer la mise à jour des titres", - "GameListContextMenuManageTitleUpdatesToolTip": "Ouvre la fenêtre de gestion de la mise à jour des titres", + "GameListContextMenuManageTitleUpdatesToolTip": "Ouvre la fenêtre de gestion des mises à jour du jeu", "GameListContextMenuManageDlc": "Gérer les DLC", "GameListContextMenuManageDlcToolTip": "Ouvre la fenêtre de gestion des DLC", - "GameListContextMenuOpenModsDirectory": "Ouvrir le dossier des Mods", - "GameListContextMenuOpenModsDirectoryToolTip": "Ouvre le dossier contenant les mods du jeu", "GameListContextMenuCacheManagement": "Gestion des caches", - "GameListContextMenuCacheManagementPurgePptc": "Purger le PPTC", - "GameListContextMenuCacheManagementPurgePptcToolTip": "Supprime le PPTC du jeu", - "GameListContextMenuCacheManagementPurgeShaderCache": "Purger le cache des Shaders", + "GameListContextMenuCacheManagementPurgePptc": "Reconstruction du PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Effectuer une reconstruction du PPTC au prochain démarrage du jeu", + "GameListContextMenuCacheManagementPurgeShaderCache": "Purger le cache des shaders", "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Supprime le cache des shaders du jeu", "GameListContextMenuCacheManagementOpenPptcDirectory": "Ouvrir le dossier du PPTC", "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Ouvre le dossier contenant le PPTC du jeu", @@ -72,15 +70,22 @@ "GameListContextMenuExtractDataRomFSToolTip": "Extrait la section RomFS du jeu (mise à jour incluse)", "GameListContextMenuExtractDataLogo": "Logo", "GameListContextMenuExtractDataLogoToolTip": "Extrait la section Logo du jeu (mise à jour incluse)", + "GameListContextMenuCreateShortcut": "Créer un raccourci d'application", + "GameListContextMenuCreateShortcutToolTip": "Créer un raccourci sur le bureau qui lance l'application sélectionnée", + "GameListContextMenuCreateShortcutToolTipMacOS": "Créer un raccourci dans le dossier Applications de macOS qui lance l'application sélectionnée", + "GameListContextMenuOpenModsDirectory": "Ouvrir le dossier des mods", + "GameListContextMenuOpenModsDirectoryToolTip": "Ouvre le dossier contenant les mods de l'application", + "GameListContextMenuOpenSdModsDirectory": "Ouvrir le dossier des mods Atmosphère", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Ouvre le dossier alternatif de la carte SD Atmosphère qui contient les mods de l'application. Utile pour les mods conçus pour du matériel réel.", "StatusBarGamesLoaded": "{0}/{1} Jeux chargés", "StatusBarSystemVersion": "Version du Firmware: {0}", - "LinuxVmMaxMapCountDialogTitle": "Limite basse pour les mappages de mémoire détectés", + "LinuxVmMaxMapCountDialogTitle": "Limite basse pour les mappings mémoire détectée", "LinuxVmMaxMapCountDialogTextPrimary": "Voulez-vous augmenter la valeur de vm.max_map_count à {0}", - "LinuxVmMaxMapCountDialogTextSecondary": "Certains jeux peuvent essayer de créer plus de mappages de mémoire que ce qui est actuellement autorisé. Ryujinx plantera dès que cette limite sera dépassée.", + "LinuxVmMaxMapCountDialogTextSecondary": "Certains jeux peuvent essayer de créer plus de mappings mémoire que ce qui est actuellement autorisé. Ryujinx plantera dès que cette limite sera dépassée.", "LinuxVmMaxMapCountDialogButtonUntilRestart": "Oui, jusqu'au prochain redémarrage", "LinuxVmMaxMapCountDialogButtonPersistent": "Oui, en permanence", "LinuxVmMaxMapCountWarningTextPrimary": "La quantité maximale de mappings mémoire est inférieure à la valeur recommandée.", - "LinuxVmMaxMapCountWarningTextSecondary": "La valeur actuelle de vm.max_map_count ({0}) est inférieure à {1}. Certains jeux peuvent essayer de créer plus de mappings mémoire que ceux actuellement autorisés. Ryujinx s'écrasera dès que cette limite sera dépassée.\n\nVous pouvez soit augmenter manuellement la limite, soit installer pkexec, ce qui permet à Ryujinx de l'aider.", + "LinuxVmMaxMapCountWarningTextSecondary": "La valeur actuelle de vm.max_map_count ({0}) est inférieure à {1}. Certains jeux peuvent essayer de créer plus de mappings mémoire que ceux actuellement autorisés. Ryujinx plantera dès que cette limite sera dépassée.\n\nVous pouvez soit augmenter manuellement la limite, soit installer pkexec, ce qui permet à Ryujinx de l'aider.", "Settings": "Paramètres", "SettingsTabGeneral": "Interface Utilisateur", "SettingsTabGeneralGeneral": "Général", @@ -91,9 +96,9 @@ "SettingsTabGeneralHideCursorNever": "Jamais", "SettingsTabGeneralHideCursorOnIdle": "Masquer le curseur si inactif", "SettingsTabGeneralHideCursorAlways": "Toujours", - "SettingsTabGeneralGameDirectories": "Dossiers de Jeux", + "SettingsTabGeneralGameDirectories": "Dossiers des jeux", "SettingsTabGeneralAdd": "Ajouter", - "SettingsTabGeneralRemove": "Supprimer", + "SettingsTabGeneralRemove": "Retirer", "SettingsTabSystem": "Système", "SettingsTabSystemCore": "Cœur", "SettingsTabSystemSystemRegion": "Région du système:", @@ -124,19 +129,19 @@ "SettingsTabSystemSystemLanguageTraditionalChinese": "Chinois traditionnel", "SettingsTabSystemSystemTimeZone": "Fuseau horaire du système :", "SettingsTabSystemSystemTime": "Heure du système:", - "SettingsTabSystemEnableVsync": "Activer le VSync", + "SettingsTabSystemEnableVsync": "Synchronisation verticale (VSync)", "SettingsTabSystemEnablePptc": "Activer le PPTC (Profiled Persistent Translation Cache)", "SettingsTabSystemEnableFsIntegrityChecks": "Activer la vérification de l'intégrité du système de fichiers", - "SettingsTabSystemAudioBackend": "Back-end audio", + "SettingsTabSystemAudioBackend": "Bibliothèque Audio :", "SettingsTabSystemAudioBackendDummy": "Factice", "SettingsTabSystemAudioBackendOpenAL": "OpenAL", "SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSDL2": "SDL2", "SettingsTabSystemHacks": "Hacks", - "SettingsTabSystemHacksNote": " (Cela peut causer des instabilités)", + "SettingsTabSystemHacksNote": "Cela peut causer des instabilités", "SettingsTabSystemExpandDramSize": "Utiliser disposition alternative de la mémoire (développeur)", "SettingsTabSystemIgnoreMissingServices": "Ignorer les services manquants", - "SettingsTabGraphics": "Graphique", + "SettingsTabGraphics": "Graphismes", "SettingsTabGraphicsAPI": "API Graphique", "SettingsTabGraphicsEnableShaderCache": "Activer le cache des shaders", "SettingsTabGraphicsAnisotropicFiltering": "Filtrage anisotrope:", @@ -150,8 +155,8 @@ "SettingsTabGraphicsResolutionScaleNative": "Natif (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "x2 (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "x3 (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "x4 (2880p/4320p)", - "SettingsTabGraphicsAspectRatio": "Format :", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Non recommandé)", + "SettingsTabGraphicsAspectRatio": "Format d'affichage :", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", "SettingsTabGraphicsAspectRatio16x10": "16:10", @@ -159,7 +164,7 @@ "SettingsTabGraphicsAspectRatio32x9": "32:9", "SettingsTabGraphicsAspectRatioStretch": "Écran étiré", "SettingsTabGraphicsDeveloperOptions": "Options développeur", - "SettingsTabGraphicsShaderDumpPath": "Chemin du dossier de dump des shaders:", + "SettingsTabGraphicsShaderDumpPath": "Chemin du dossier de copie des shaders:", "SettingsTabLogging": "Journaux", "SettingsTabLoggingLogging": "Journaux", "SettingsTabLoggingEnableLoggingToFile": "Activer la sauvegarde des journaux vers un fichier", @@ -169,9 +174,9 @@ "SettingsTabLoggingEnableErrorLogs": "Activer les journaux d'erreurs", "SettingsTabLoggingEnableTraceLogs": "Activer journaux d'erreurs Trace", "SettingsTabLoggingEnableGuestLogs": "Activer les journaux du programme simulé", - "SettingsTabLoggingEnableFsAccessLogs": "Activer les journaux des accès au système de fichiers", - "SettingsTabLoggingFsGlobalAccessLogMode": "Niveau des journaux des accès au système de fichiers:", - "SettingsTabLoggingDeveloperOptions": "Options développeur (ATTENTION: Cela peut réduire les performances)", + "SettingsTabLoggingEnableFsAccessLogs": "Activer les journaux d'accès au système de fichiers", + "SettingsTabLoggingFsGlobalAccessLogMode": "Niveau des journaux d'accès au système de fichiers:", + "SettingsTabLoggingDeveloperOptions": "Options développeur", "SettingsTabLoggingDeveloperOptionsNote": "ATTENTION : Réduira les performances", "SettingsTabLoggingGraphicsBackendLogLevel": "Niveau du journal du backend graphique :", "SettingsTabLoggingGraphicsBackendLogLevelNone": "Aucun", @@ -200,7 +205,7 @@ "ControllerSettingsInputDevice": "Périphériques", "ControllerSettingsRefresh": "Actualiser", "ControllerSettingsDeviceDisabled": "Désactivé", - "ControllerSettingsControllerType": "Type de Controleur", + "ControllerSettingsControllerType": "Type de contrôleur", "ControllerSettingsControllerTypeHandheld": "Portable", "ControllerSettingsControllerTypeProController": "Pro Controller", "ControllerSettingsControllerTypeJoyConPair": "JoyCon Joints", @@ -218,7 +223,7 @@ "ControllerSettingsButtonY": "Y", "ControllerSettingsButtonPlus": "+", "ControllerSettingsButtonMinus": "-", - "ControllerSettingsDPad": "Croix Directionnelle", + "ControllerSettingsDPad": "Croix directionnelle", "ControllerSettingsDPadUp": "Haut", "ControllerSettingsDPadDown": "Bas", "ControllerSettingsDPadLeft": "Gauche", @@ -228,7 +233,7 @@ "ControllerSettingsStickDown": "Bas", "ControllerSettingsStickLeft": "Gauche", "ControllerSettingsStickRight": "Droite", - "ControllerSettingsStickStick": "Stick", + "ControllerSettingsStickStick": "Joystick", "ControllerSettingsStickInvertXAxis": "Inverser l'axe X", "ControllerSettingsStickInvertYAxis": "Inverser l'axe Y", "ControllerSettingsStickDeadzone": "Zone morte :", @@ -256,16 +261,16 @@ "ControllerSettingsMotionControllerSlot": "Contrôleur ID:", "ControllerSettingsMotionMirrorInput": "Inverser les contrôles", "ControllerSettingsMotionRightJoyConSlot": "JoyCon Droit ID:", - "ControllerSettingsMotionServerHost": "Addresse du Server:", + "ControllerSettingsMotionServerHost": "Serveur d'hébergement :", "ControllerSettingsMotionGyroSensitivity": "Sensibilitée du gyroscope:", "ControllerSettingsMotionGyroDeadzone": "Zone morte du gyroscope:", "ControllerSettingsSave": "Enregistrer", "ControllerSettingsClose": "Fermer", - "UserProfilesSelectedUserProfile": "Choisir un profil utilisateur:", + "UserProfilesSelectedUserProfile": "Profil utilisateur sélectionné :", "UserProfilesSaveProfileName": "Enregistrer le nom du profil", "UserProfilesChangeProfileImage": "Changer l'image du profil", - "UserProfilesAvailableUserProfiles": "Profils utilisateurs disponible:", - "UserProfilesAddNewProfile": "Ajouter un nouveau profil", + "UserProfilesAvailableUserProfiles": "Profils utilisateurs disponibles:", + "UserProfilesAddNewProfile": "Créer un profil", "UserProfilesDelete": "Supprimer", "UserProfilesClose": "Fermer", "ProfileNameSelectionWatermark": "Choisir un pseudo", @@ -280,25 +285,21 @@ "InputDialogAddNewProfileTitle": "Choisir un nom de profil", "InputDialogAddNewProfileHeader": "Merci d'entrer un nom de profil", "InputDialogAddNewProfileSubtext": "(Longueur max.: {0})", - "AvatarChoose": "Choisir", + "AvatarChoose": "Choisir un avatar", "AvatarSetBackgroundColor": "Choisir une couleur de fond", "AvatarClose": "Fermer", "ControllerSettingsLoadProfileToolTip": "Charger un profil", "ControllerSettingsAddProfileToolTip": "Ajouter un profil", "ControllerSettingsRemoveProfileToolTip": "Supprimer un profil", "ControllerSettingsSaveProfileToolTip": "Enregistrer un profil", - "MenuBarFileToolsTakeScreenshot": "Prendre une Capture d'Écran", + "MenuBarFileToolsTakeScreenshot": "Prendre une capture d'écran", "MenuBarFileToolsHideUi": "Masquer l'interface utilisateur", "GameListContextMenuRunApplication": "Démarrer l'application", "GameListContextMenuToggleFavorite": "Ajouter/Retirer des favoris", "GameListContextMenuToggleFavoriteToolTip": "Activer/désactiver le statut favori du jeu", - "SettingsTabGeneralTheme": "Thème", - "SettingsTabGeneralThemeCustomTheme": "Chemin du thème personnalisé", - "SettingsTabGeneralThemeBaseStyle": "Style par défaut", - "SettingsTabGeneralThemeBaseStyleDark": "Sombre", - "SettingsTabGeneralThemeBaseStyleLight": "Lumière", - "SettingsTabGeneralThemeEnableCustomTheme": "Activer un Thème Personnalisé", - "ButtonBrowse": "Parcourir", + "SettingsTabGeneralTheme": "Thème :", + "SettingsTabGeneralThemeDark": "Sombre", + "SettingsTabGeneralThemeLight": "Clair", "ControllerSettingsConfigureGeneral": "Configurer", "ControllerSettingsRumble": "Vibreur", "ControllerSettingsRumbleStrongMultiplier": "Multiplicateur de vibrations fortes", @@ -312,7 +313,7 @@ "DialogExitTitle": "Ryujinx - Quitter", "DialogErrorMessage": "Ryujinx a rencontré une erreur", "DialogExitMessage": "Êtes-vous sûr de vouloir fermer Ryujinx ?", - "DialogExitSubMessage": "Toute progression non sauvegardée sera perdue.", + "DialogExitSubMessage": "Toutes les données non enregistrées seront perdues !", "DialogMessageCreateSaveErrorMessage": "Une erreur s'est produite lors de la création de la sauvegarde spécifiée : {0}", "DialogMessageFindSaveErrorMessage": "Une erreur s'est produite lors de la recherche de la sauvegarde spécifiée : {0}", "FolderDialogExtractTitle": "Choisissez le dossier dans lequel extraire", @@ -322,8 +323,8 @@ "DialogNcaExtractionCheckLogErrorMessage": "Échec de l'extraction. Lisez le fichier journal pour plus d'informations.", "DialogNcaExtractionSuccessMessage": "Extraction terminée avec succès.", "DialogUpdaterConvertFailedMessage": "Échec de la conversion de la version actuelle de Ryujinx.", - "DialogUpdaterCancelUpdateMessage": "Annuler la mise à jour!", - "DialogUpdaterAlreadyOnLatestVersionMessage": "Vous utilisez déjà la version la plus mise à jour de Ryujinx!", + "DialogUpdaterCancelUpdateMessage": "Annuler la mise à jour !", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Vous utilisez déjà la version la plus récente de Ryujinx !", "DialogUpdaterFailedToGetVersionMessage": "Une erreur s'est produite lors de la tentative d'obtention des informations de publication de la version GitHub. Cela peut survenir lorsqu'une nouvelle version est en cours de compilation par GitHub Actions. Réessayez dans quelques minutes.", "DialogUpdaterConvertFailedGithubMessage": "Impossible de convertir la version reçue de Ryujinx depuis Github Release.", "DialogUpdaterDownloadingMessage": "Téléchargement de la mise à jour...", @@ -332,13 +333,11 @@ "DialogUpdaterAddingFilesMessage": "Ajout d'une nouvelle mise à jour...", "DialogUpdaterCompleteMessage": "Mise à jour terminée !", "DialogUpdaterRestartMessage": "Voulez-vous redémarrer Ryujinx maintenant ?", - "DialogUpdaterArchNotSupportedMessage": "Vous n'utilisez pas d'architecture système prise en charge !", - "DialogUpdaterArchNotSupportedSubMessage": "(Seuls les systèmes x64 sont pris en charge !)", "DialogUpdaterNoInternetMessage": "Vous n'êtes pas connecté à Internet !", - "DialogUpdaterNoInternetSubMessage": "Veuillez vérifier que vous avez une connexion Internet fonctionnelle!", - "DialogUpdaterDirtyBuildMessage": "Vous ne pouvez pas mettre à jour une version Dirty de Ryujinx!", + "DialogUpdaterNoInternetSubMessage": "Veuillez vérifier que vous disposez d'une connexion Internet fonctionnelle !", + "DialogUpdaterDirtyBuildMessage": "Vous ne pouvez pas mettre à jour une version Dirty de Ryujinx !", "DialogUpdaterDirtyBuildSubMessage": "Veuillez télécharger Ryujinx sur https://ryujinx.org/ si vous recherchez une version prise en charge.", - "DialogRestartRequiredMessage": "Redémarrage Requis", + "DialogRestartRequiredMessage": "Redémarrage requis", "DialogThemeRestartMessage": "Le thème a été enregistré. Un redémarrage est requis pour appliquer le thème.", "DialogThemeRestartSubMessage": "Voulez-vous redémarrer", "DialogFirmwareInstallEmbeddedMessage": "Voulez-vous installer le firmware intégré dans ce jeu ? (Firmware {0})", @@ -384,8 +383,11 @@ "DialogUserProfileUnsavedChangesMessage": "Vous avez effectué des modifications sur ce profil d'utilisateur qui n'ont pas été enregistrées.", "DialogUserProfileUnsavedChangesSubMessage": "Voulez-vous annuler les modifications ?", "DialogControllerSettingsModifiedConfirmMessage": "Les paramètres actuels du contrôleur ont été mis à jour.", - "DialogControllerSettingsModifiedConfirmSubMessage": "Voulez-vous sauvegarder?", - "DialogLoadNcaErrorMessage": "{0}. Fichier erroné : {1}", + "DialogControllerSettingsModifiedConfirmSubMessage": "Voulez-vous sauvegarder ?", + "DialogLoadFileErrorMessage": "{0}. Fichier erroné : {1}", + "DialogModAlreadyExistsMessage": "Le mod existe déjà", + "DialogModInvalidMessage": "Le répertoire spécifié ne contient pas de mod !", + "DialogModDeleteNoParentMessage": "Impossible de supprimer : impossible de trouver le répertoire parent pour le mod \"{0} \" !", "DialogDlcNoDlcErrorMessage": "Le fichier spécifié ne contient pas de DLC pour le titre sélectionné !", "DialogPerformanceCheckLoggingEnabledMessage": "Vous avez activé la journalisation des traces, conçue pour être utilisée uniquement par les développeurs.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Pour des performances optimales, il est recommandé de désactiver la journalisation des traces. Souhaitez-vous désactiver la journalisation des traces maintenant ?", @@ -396,6 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "Le fichier spécifié ne contient pas de mise à jour pour le titre sélectionné !", "DialogSettingsBackendThreadingWarningTitle": "Avertissement - Backend Threading ", "DialogSettingsBackendThreadingWarningMessage": "Ryujinx doit être redémarré après avoir changé cette option pour qu'elle s'applique complètement. Selon votre plate-forme, vous devrez peut-être désactiver manuellement le multithreading de votre pilote lorsque vous utilisez Ryujinx.", + "DialogModManagerDeletionWarningMessage": "Vous êtes sur le point de supprimer le mod : {0}\n\nÊtes-vous sûr de vouloir continuer ?", + "DialogModManagerDeletionAllWarningMessage": "Vous êtes sur le point de supprimer tous les mods pour ce titre.\n\nÊtes-vous sûr de vouloir continuer ?", "SettingsTabGraphicsFeaturesOptions": "Fonctionnalités", "SettingsTabGraphicsBackendMultithreading": "Interface graphique multithread", "CommonAuto": "Auto", @@ -413,7 +417,7 @@ "AboutGithubUrlTooltipMessage": "Cliquez pour ouvrir la page GitHub de Ryujinx dans votre navigateur par défaut.", "AboutDiscordUrlTooltipMessage": "Cliquez pour ouvrir une invitation au serveur Discord de Ryujinx dans votre navigateur par défaut.", "AboutTwitterUrlTooltipMessage": "Cliquez pour ouvrir la page Twitter de Ryujinx dans votre navigateur par défaut.", - "AboutRyujinxAboutTitle": "Á propos:", + "AboutRyujinxAboutTitle": "À propos :", "AboutRyujinxAboutContent": "Ryujinx est un émulateur pour la Nintendo Switch™.\nMerci de nous soutenir sur Patreon.\nObtenez toutes les dernières actualités sur notre Twitter ou notre Discord.\nLes développeurs intéressés à contribuer peuvent en savoir plus sur notre GitHub ou notre Discord.", "AboutRyujinxMaintainersTitle": "Maintenu par :", "AboutRyujinxMaintainersContentTooltipMessage": "Cliquez pour ouvrir la page Contributeurs dans votre navigateur par défaut.", @@ -428,9 +432,10 @@ "DlcManagerTableHeadingContainerPathLabel": "Chemin du conteneur", "DlcManagerTableHeadingFullPathLabel": "Chemin complet", "DlcManagerRemoveAllButton": "Tout supprimer", - "DlcManagerEnableAllButton": "Activer Tout", - "DlcManagerDisableAllButton": "Désactiver Tout", - "MenuBarOptionsChangeLanguage": "Changer la Langue", + "DlcManagerEnableAllButton": "Tout activer", + "DlcManagerDisableAllButton": "Tout désactiver", + "ModManagerDeleteAllButton": "Tout supprimer", + "MenuBarOptionsChangeLanguage": "Changer la langue", "MenuBarShowFileTypes": "Afficher les types de fichiers", "CommonSort": "Trier", "CommonShowNames": "Afficher les noms", @@ -442,18 +447,18 @@ "ToggleDiscordTooltip": "Choisissez d'afficher ou non Ryujinx sur votre activité « en cours de jeu » Discord", "AddGameDirBoxTooltip": "Entrez un répertoire de jeux à ajouter à la liste", "AddGameDirTooltip": "Ajouter un répertoire de jeux à la liste", - "RemoveGameDirTooltip": "Supprimer le dossier sélectionné", + "RemoveGameDirTooltip": "Supprimer le répertoire de jeu sélectionné", "CustomThemeCheckTooltip": "Utilisez un thème personnalisé Avalonia pour modifier l'apparence des menus de l'émulateur", "CustomThemePathTooltip": "Chemin vers le thème personnalisé de l'interface utilisateur", "CustomThemeBrowseTooltip": "Parcourir vers un thème personnalisé pour l'interface utilisateur", "DockModeToggleTooltip": "Le mode station d'accueil permet à la console émulée de se comporter comme une Nintendo Switch en mode station d'accueil, ce qui améliore la fidélité graphique dans la plupart des jeux. Inversement, la désactivation de cette option rendra la console émulée comme une console Nintendo Switch portable, réduisant la qualité graphique.\n\nConfigurer les controles du joueur 1 si vous prévoyez d'utiliser le mode station d'accueil; configurez les commandes portable si vous prévoyez d'utiliser le mode portable.\n\nLaissez ACTIVER si vous n'êtes pas sûr.", - "DirectKeyboardTooltip": "Prise en charge de l'accès direct au clavier (HID). Fournit aux jeux l'accès à votre clavier en tant que périphérique de saisie de texte.", - "DirectMouseTooltip": "Prise en charge de l'accès à la souris (HID). Permet aux jeux d'accéder a votre souris en tant que périphérique de pointage.", + "DirectKeyboardTooltip": "Prise en charge de l'accès direct au clavier (HID). Permet aux jeux d'accéder à votre clavier comme périphérique de saisie de texte.\n\nFonctionne uniquement avec les jeux prenant en charge nativement l'utilisation du clavier sur le matériel Switch.\n\nLaissez OFF si vous n'êtes pas sûr.", + "DirectMouseTooltip": "Prise en charge de l'accès direct à la souris (HID). Permet aux jeux d'accéder à votre souris en tant que dispositif de pointage.\n\nFonctionne uniquement avec les jeux qui prennent en charge nativement les contrôles de souris sur le matériel Switch, ce qui est rare.\n\nLorsqu'il est activé, la fonctionnalité de l'écran tactile peut ne pas fonctionner.\n\nLaissez sur OFF si vous n'êtes pas sûr.", "RegionTooltip": "Changer la région du système", "LanguageTooltip": "Changer la langue du système", "TimezoneTooltip": "Changer le fuseau horaire du système", "TimeTooltip": "Changer l'heure du système", - "VSyncToggleTooltip": "Synchronisation verticale de l'émulateur. Généralement un limiteur d'FPS pour la majorité des jeux, le désactiver peut accélérer les jeux pour rendre les écrans de chargement plus court, mais augemente le risque de crash lors des chargements.\n\nPeut être activer ou desactiver en jeu avec un raccourci de votre choix. Nous vous recommandons de le laisser.\n\nLaissez activer, si vous n'êtes pas sûr.", + "VSyncToggleTooltip": "La synchronisation verticale de la console émulée. Essentiellement un limiteur de trame pour la majorité des jeux ; le désactiver peut entraîner un fonctionnement plus rapide des jeux ou prolonger ou bloquer les écrans de chargement.\n\nPeut être activé ou désactivé en jeu avec un raccourci clavier de votre choix (F1 par défaut). Nous recommandons de le faire si vous envisagez de le désactiver.\n\nLaissez activé si vous n'êtes pas sûr.", "PptcToggleTooltip": "Sauvegarde les fonctions JIT afin qu'elles n'aient pas besoin d'être à chaque fois recompiler lorsque le jeu se charge.\n\nRéduit les lags et accélère considérablement le temps de chargement après le premier lancement d'un jeu.\n\nLaissez par défaut si vous n'êtes pas sûr.", "FsIntegrityToggleTooltip": "Vérifie si des fichiers sont corrompus lors du lancement d'un jeu, et si des fichiers corrompus sont détectés, affiche une erreur de hachage dans la console.\n\nN'a aucun impact sur les performances et est destiné à aider le dépannage.\n\nLaissez activer en cas d'incertitude.", "AudioBackendTooltip": "Modifie le backend utilisé pour donnée un rendu audio.\n\nSDL2 est préféré, tandis que OpenAL et SoundIO sont utilisés comme backend secondaire. Le backend Dummy (Factice) ne rends aucun son.\n\nLaissez sur SDL2 si vous n'êtes pas sûr.", @@ -467,10 +472,10 @@ "GraphicsBackendThreadingTooltip": "Exécute des commandes du backend graphiques sur un second thread.\n\nAccélère la compilation des shaders, réduit les crashs et les lags, améliore les performances sur les pilotes GPU sans support de multithreading. Légère augementation des performances sur les pilotes avec multithreading intégrer.\n\nRéglez sur Auto en cas d'incertitude.", "GalThreadingTooltip": "Exécute des commandes du backend graphiques sur un second thread.\n\nAccélère la compilation des shaders, réduit les crashs et les lags, améliore les performances sur les pilotes GPU sans support de multithreading. Légère augementation des performances sur les pilotes avec multithreading intégrer.\n\nRéglez sur Auto en cas d'incertitude.", "ShaderCacheToggleTooltip": "Enregistre un cache de shaders sur le disque dur, réduit le lag lors de multiples exécutions.\n\nLaissez Activer si vous n'êtes pas sûr.", - "ResolutionScaleTooltip": "Échelle de résolution appliquer au rendu du jeu", + "ResolutionScaleTooltip": "Multiplie la résolution de rendu du jeu.\n\nQuelques jeux peuvent ne pas fonctionner avec cette fonctionnalité et sembler pixelisés même lorsque la résolution est augmentée ; pour ces jeux, vous devrez peut-être trouver des mods qui suppriment l'anti-aliasing ou qui augmentent leur résolution de rendu interne. Pour utiliser cette dernière option, vous voudrez probablement sélectionner \"Native\".\n\nCette option peut être modifiée pendant qu'un jeu est en cours d'exécution en cliquant sur \"Appliquer\" ci-dessous ; vous pouvez simplement déplacer la fenêtre des paramètres de côté et expérimenter jusqu'à ce que vous trouviez l'apparence souhaitée pour un jeu.\n\nGardez à l'esprit que 4x est excessif pour pratiquement n'importe quelle configuration.", "ResolutionScaleEntryTooltip": "Échelle de résolution à virgule flottante, telle que : 1.5. Les échelles non intégrales sont plus susceptibles de causer des problèmes ou des crashs.", - "AnisotropyTooltip": "Niveau de Filtrage Anisotropique (mettre sur Auto pour utiliser la valeur demandée par le jeu)", - "AspectRatioTooltip": "Ratio d'aspect appliqué à la fenêtre de rendu", + "AnisotropyTooltip": "Niveau de filtrage anisotrope. Réglez sur Auto pour utiliser la valeur demandée par le jeu.", + "AspectRatioTooltip": "Rapport d'aspect appliqué à la fenêtre du moteur de rendu.\n\nChangez cela uniquement si vous utilisez un mod de rapport d'aspect pour votre jeu, sinon les graphismes seront étirés.\n\nLaissez sur 16:9 si vous n'êtes pas sûr.", "ShaderDumpPathTooltip": "Chemin de copie des Shaders Graphiques", "FileLogTooltip": "Sauver le journal de la console dans un fichier journal sur le disque. Cela n'affecte pas les performances.", "StubLogTooltip": "Affiche les messages de log dans la console. N'affecte pas les performances.", @@ -504,7 +509,9 @@ "EnableInternetAccessTooltip": "Permet à l'application émulée de se connecter à Internet.\n\nLes jeux avec un mode LAN peuvent se connecter les uns aux autres lorsque cette option est cochée et que les systèmes sont connectés au même point d'accès. Cela inclut également les vrais consoles.\n\nCette option n'autorise PAS la connexion aux serveurs Nintendo. Elle peut faire planter certains jeux qui essaient de se connecter à l'Internet.\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr.", "GameListContextMenuManageCheatToolTip": "Gérer la triche", "GameListContextMenuManageCheat": "Gérer la triche", - "ControllerSettingsStickRange": "Intervalle:", + "GameListContextMenuManageModToolTip": "Gérer les mods", + "GameListContextMenuManageMod": "Gérer les mods", + "ControllerSettingsStickRange": "Intervalle :", "DialogStopEmulationTitle": "Ryujinx - Arrêt de l'émulation", "DialogStopEmulationMessage": "Êtes-vous sûr de vouloir arrêter l'émulation ?", "SettingsTabCpu": "CPU", @@ -515,9 +522,7 @@ "SettingsTabCpuMemory": "Mémoire CPU", "DialogUpdaterFlatpakNotSupportedMessage": "Merci de mettre à jour Ryujinx via FlatHub.", "UpdaterDisabledWarningTitle": "Mise à jour désactivée !", - "GameListContextMenuOpenSdModsDirectory": "Ouvrir le dossier Mods d'Atmosphère", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Ouvre le répertoire alternatif de carte SD Atmosphère qui contient les Mods d'Application. Utile pour les mods qui sont conçu pour le vrai matériel.", - "ControllerSettingsRotate90": "Rotation 90° horaire", + "ControllerSettingsRotate90": "Faire pivoter de 90° à droite", "IconSize": "Taille d'icône", "IconSizeTooltip": "Changer la taille des icônes de jeu", "MenuBarOptionsShowConsole": "Afficher la console", @@ -532,38 +537,39 @@ "UserErrorNoFirmwareDescription": "Ryujinx n'a pas trouvé de firmwares installés", "UserErrorFirmwareParsingFailedDescription": "Ryujinx n'a pas pu analyser le firmware fourni. Cela est généralement dû à des clés obsolètes.", "UserErrorApplicationNotFoundDescription": "Ryujinx n'a pas pu trouver une application valide dans le chemin indiqué.", - "UserErrorUnknownDescription": "Une erreur inconnue est survenue!", + "UserErrorUnknownDescription": "Une erreur inconnue est survenue !", "UserErrorUndefinedDescription": "Une erreur inconnue est survenue ! Cela ne devrait pas se produire, merci de contacter un développeur !", "OpenSetupGuideMessage": "Ouvrir le guide d'installation", "NoUpdate": "Aucune mise à jour", - "TitleUpdateVersionLabel": "Version {0} - {1}", + "TitleUpdateVersionLabel": "Version {0}", "RyujinxInfo": "Ryujinx - Info", "RyujinxConfirm": "Ryujinx - Confirmation", "FileDialogAllTypes": "Tous les types", "Never": "Jamais", "SwkbdMinCharacters": "Doit comporter au moins {0} caractères", - "SwkbdMinRangeCharacters": "Doit contenir {0}-{1} caractères en longueur", + "SwkbdMinRangeCharacters": "Doit comporter entre {0} et {1} caractères", "SoftwareKeyboard": "Clavier logiciel", - "SoftwareKeyboardModeNumbersOnly": "Doit être uniquement des chiffres", + "SoftwareKeyboardModeNumeric": "Doit être 0-9 ou '.' uniquement", "SoftwareKeyboardModeAlphabet": "Doit être uniquement des caractères non CJK", "SoftwareKeyboardModeASCII": "Doit être uniquement du texte ASCII", - "DialogControllerAppletMessagePlayerRange": "L'application demande {0} joueur(s) avec :\n\nTYPES : {1}\n\nJOUEURS : {2}\n\n{3}Merci d'ouvrir les Paramètres et de reconfigurer les Périphériques maintenant ou appuyez sur Fermer.", - "DialogControllerAppletMessage": "L'application demande exactement {0} joueur(s) avec :\n\nTYPES : {1}\n\nJOUEURS : {2}\n\n{3}Merci d'ouvrir les Paramètres et de reconfigurer les Périphériques maintenant ou appuyez sur Fermer.", - "DialogControllerAppletDockModeSet": "Mode station d'accueil défini. Le portable est également invalide.\n\n", + "ControllerAppletControllers": "Contrôleurs pris en charge :", + "ControllerAppletPlayers": "Joueurs :", + "ControllerAppletDescription": "Votre configuration actuelle n'est pas valide. Ouvrez les paramètres et reconfigurez vos contrôles.", + "ControllerAppletDocked": "Mode station d'accueil défini. Le mode contrôle portable doit être désactivé.", "UpdaterRenaming": "Renommage des anciens fichiers...", "UpdaterRenameFailed": "Impossible de renommer le fichier : {0}", "UpdaterAddingFiles": "Ajout des nouveaux fichiers...", "UpdaterExtracting": "Extraction de la mise à jour…", "UpdaterDownloading": "Téléchargement de la mise à jour...", "Game": "Jeu", - "Docked": "Attaché", - "Handheld": "Portable", + "Docked": "Mode station d'accueil", + "Handheld": "Mode Portable", "ConnectionError": "Erreur de connexion.", "AboutPageDeveloperListMore": "{0} et plus...", "ApiError": "Erreur API.", "LoadingHeading": "Chargement {0}", "CompilingPPTC": "Compilation PTC", - "CompilingShaders": "Compilation des Shaders", + "CompilingShaders": "Compilation des shaders", "AllKeyboards": "Tous les claviers", "OpenFileDialogTitle": "Sélectionnez un fichier supporté à ouvrir", "OpenFolderDialogTitle": "Sélectionnez un dossier avec un jeu décompressé", @@ -572,13 +578,13 @@ "SettingsTabHotkeys": "Raccourcis clavier", "SettingsTabHotkeysHotkeys": "Raccourcis clavier", "SettingsTabHotkeysToggleVsyncHotkey": "Activer/désactiver la VSync :", - "SettingsTabHotkeysScreenshotHotkey": "Captures d'écran :", + "SettingsTabHotkeysScreenshotHotkey": "Capture d'écran :", "SettingsTabHotkeysShowUiHotkey": "Afficher UI :", "SettingsTabHotkeysPauseHotkey": "Suspendre :", "SettingsTabHotkeysToggleMuteHotkey": "Muet : ", "ControllerMotionTitle": "Réglages du contrôle par mouvement", - "ControllerRumbleTitle": "Paramètres de Vibration", - "SettingsSelectThemeFileDialogTitle": "Sélectionnez un Fichier de Thème", + "ControllerRumbleTitle": "Paramètres de vibration", + "SettingsSelectThemeFileDialogTitle": "Sélectionner un fichier de thème", "SettingsXamlThemeFile": "Fichier thème Xaml", "AvatarWindowTitle": "Gérer les Comptes - Avatar", "Amiibo": "Amiibo", @@ -587,46 +593,49 @@ "Writable": "Ecriture possible", "SelectDlcDialogTitle": "Sélectionner les fichiers DLC", "SelectUpdateDialogTitle": "Sélectionner les fichiers de mise à jour", + "SelectModDialogTitle": "Sélectionner le répertoire du mod", "UserProfileWindowTitle": "Gestionnaire de profils utilisateur", "CheatWindowTitle": "Gestionnaire de triches", - "DlcWindowTitle": "Gestionnaire de contenus téléchargeables", + "DlcWindowTitle": "Gérer le contenu téléchargeable pour {0} ({1})", "UpdateWindowTitle": "Gestionnaire de mises à jour", "CheatWindowHeading": "Cheats disponibles pour {0} [{1}]", "BuildId": "BuildId:", - "DlcWindowHeading": "{0} Contenu(s) téléchargeable(s) disponible pour {1} ({2})", + "DlcWindowHeading": "{0} Contenu(s) téléchargeable(s)", + "ModWindowHeading": "{0} Mod(s)", "UserProfilesEditProfile": "Éditer la sélection", "Cancel": "Annuler", "Save": "Enregistrer", "Discard": "Abandonner", - "UserProfilesSetProfileImage": "Modifier l'image du profil", + "Paused": "Suspendu", + "UserProfilesSetProfileImage": "Définir l'image de profil", "UserProfileEmptyNameError": "Le nom est requis", "UserProfileNoImageError": "L'image du profil doit être définie", - "GameUpdateWindowHeading": "{0} mise(s) à jour disponible pour {1} ({2})", - "SettingsTabHotkeysResScaleUpHotkey": "Augmenter la résolution:", + "GameUpdateWindowHeading": "Gérer les mises à jour pour {0} ({1})", + "SettingsTabHotkeysResScaleUpHotkey": "Augmenter la résolution :", "SettingsTabHotkeysResScaleDownHotkey": "Diminuer la résolution :", "UserProfilesName": "Nom :", "UserProfilesUserId": "Identifiant de l'utilisateur :", "SettingsTabGraphicsBackend": "API de Rendu", - "SettingsTabGraphicsBackendTooltip": "Interface Graphique à utiliser", + "SettingsTabGraphicsBackendTooltip": "Sélectionnez le moteur graphique qui sera utilisé dans l'émulateur.\n\nVulkan est globalement meilleur pour toutes les cartes graphiques modernes, tant que leurs pilotes sont à jour. Vulkan offre également une compilation de shaders plus rapide (moins de saccades) sur tous les fournisseurs de GPU.\n\nOpenGL peut obtenir de meilleurs résultats sur d'anciennes cartes graphiques Nvidia, sur d'anciennes cartes graphiques AMD sous Linux, ou sur des GPU avec moins de VRAM, bien que les saccades dues à la compilation des shaders soient plus importantes.\n\nRéglez sur Vulkan si vous n'êtes pas sûr. Réglez sur OpenGL si votre GPU ne prend pas en charge Vulkan même avec les derniers pilotes graphiques.", "SettingsEnableTextureRecompression": "Activer la recompression des textures", - "SettingsEnableTextureRecompressionTooltip": "Compresse certaines textures afin de réduire l'utilisation de la VRAM.\n\nRecommandé pour une utilisation avec des GPU qui ont moins de 4 Go de VRAM.\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr.", + "SettingsEnableTextureRecompressionTooltip": "Les jeux utilisant ce format de texture incluent Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder et The Legend of Zelda: Tears of the Kingdom.\n\nLes cartes graphiques avec 4 Go ou moins de VRAM risquent probablement de planter à un moment donné lors de l'exécution de ces jeux.\n\nActivez uniquement si vous manquez de VRAM sur les jeux mentionnés ci-dessus. Laissez DÉSACTIVÉ si vous n'êtes pas sûr.", "SettingsTabGraphicsPreferredGpu": "GPU préféré", "SettingsTabGraphicsPreferredGpuTooltip": "Sélectionnez la carte graphique qui sera utilisée avec l'interface graphique Vulkan.\n\nCela ne change pas le GPU qu'OpenGL utilisera.\n\nChoisissez le GPU noté \"dGPU\" si vous n'êtes pas sûr. S'il n'y en a pas, ne pas modifier.", "SettingsAppRequiredRestartMessage": "Redémarrage de Ryujinx requis", "SettingsGpuBackendRestartMessage": "Les paramètres de l'interface graphique ou du GPU ont été modifiés. Cela nécessitera un redémarrage pour être appliqué", - "SettingsGpuBackendRestartSubMessage": "\n\nVoulez-vous redémarrer maintenant?", + "SettingsGpuBackendRestartSubMessage": "\n\nVoulez-vous redémarrer maintenant ?", "RyujinxUpdaterMessage": "Voulez-vous mettre à jour Ryujinx vers la dernière version ?", "SettingsTabHotkeysVolumeUpHotkey": "Augmenter le volume :", "SettingsTabHotkeysVolumeDownHotkey": "Diminuer le volume :", "SettingsEnableMacroHLE": "Activer les macros HLE", - "SettingsEnableMacroHLETooltip": "Émulation de haut niveau du code de Macro GPU.\n\nAméliore les performances, mais peut causer des bugs graphiques dans certains jeux.\n\nLaissez ACTIVER si vous n'êtes pas sûr.", + "SettingsEnableMacroHLETooltip": "Émulation de haut niveau du code de Macro GPU.\n\nAméliore les performances, mais peut causer des artefacts graphiques dans certains jeux.\n\nLaissez ACTIVER si vous n'êtes pas sûr.", "SettingsEnableColorSpacePassthrough": "Traversée de l'espace colorimétrique", - "SettingsEnableColorSpacePassthroughTooltip": "Dirige l'interface graphique Vulkan pour qu'il transmette les informations de couleur sans spécifier d'espace colorimétrique. Pour les utilisateurs possédant des écrans haut de gamme, cela peut entraîner des couleurs plus vives, au détriment de l'exactitude des couleurs.", + "SettingsEnableColorSpacePassthroughTooltip": "Dirige l'interface graphique Vulkan pour qu'il transmette les informations de couleur sans spécifier d'espace colorimétrique. Pour les utilisateurs possédant des écrans Wide Color Gamut, cela peut entraîner des couleurs plus vives, au détriment de l'exactitude des couleurs.", "VolumeShort": "Vol", "UserProfilesManageSaves": "Gérer les sauvegardes", - "DeleteUserSave": "Voulez-vous supprimer la sauvegarde de l'utilisateur pour ce jeu?", + "DeleteUserSave": "Voulez-vous supprimer la sauvegarde de l'utilisateur pour ce jeu ?", "IrreversibleActionNote": "Cette action n'est pas réversible.", - "SaveManagerHeading": "Gérer les sauvegardes pour {0}", + "SaveManagerHeading": "Gérer les sauvegardes pour {0} ({1})", "SaveManagerTitle": "Gestionnaire de sauvegarde", "Name": "Nom ", "Size": "Taille", @@ -635,12 +644,12 @@ "Recover": "Récupérer", "UserProfilesRecoverHeading": "Des sauvegardes ont été trouvées pour les comptes suivants", "UserProfilesRecoverEmptyList": "Aucun profil à restaurer", - "GraphicsAATooltip": "Applique l'anticrénelage au rendu du jeu", + "GraphicsAATooltip": "FXAA floute la plupart de l'image, tandis que SMAA tente de détecter les contours dentelés et de les lisser.\n\nIl n'est pas recommandé de l'utiliser en conjonction avec le filtre de mise à l'échelle FSR.\n\nCette option peut être modifiée pendant qu'un jeu est en cours d'exécution en cliquant sur \"Appliquer\" ci-dessous ; vous pouvez simplement déplacer la fenêtre des paramètres de côté et expérimenter jusqu'à ce que vous trouviez l'apparence souhaitée pour un jeu.\n\nLaissez sur NONE si vous n'êtes pas sûr.", "GraphicsAALabel": "Anticrénelage :", "GraphicsScalingFilterLabel": "Filtre de mise à l'échelle :", - "GraphicsScalingFilterTooltip": "Active la mise à l'échelle du tampon d'image", + "GraphicsScalingFilterTooltip": "Choisissez le filtre de mise à l'échelle qui sera appliqué lors de l'utilisation de la mise à l'échelle de la résolution.\n\nLe filtre bilinéaire fonctionne bien pour les jeux en 3D et constitue une option par défaut sûre.\n\nLe filtre le plus proche est recommandé pour les jeux de pixel art.\n\nFSR 1.0 est simplement un filtre de netteté, non recommandé pour une utilisation avec FXAA ou SMAA.\n\nCette option peut être modifiée pendant qu'un jeu est en cours d'exécution en cliquant sur \"Appliquer\" ci-dessous ; vous pouvez simplement déplacer la fenêtre des paramètres de côté et expérimenter jusqu'à ce que vous trouviez l'aspect souhaité pour un jeu.\n\nLaissez sur BILINEAR si vous n'êtes pas sûr.", "GraphicsScalingFilterLevelLabel": "Niveau ", - "GraphicsScalingFilterLevelTooltip": "Définir le niveau du filtre de mise à l'échelle", + "GraphicsScalingFilterLevelTooltip": "Définissez le niveau de netteté FSR 1.0. Plus élevé signifie plus net.", "SmaaLow": "SMAA Faible", "SmaaMedium": "SMAA moyen", "SmaaHigh": "SMAA Élevé", @@ -648,9 +657,12 @@ "UserEditorTitle": "Modifier Utilisateur", "UserEditorTitleCreate": "Créer Utilisateur", "SettingsTabNetworkInterface": "Interface Réseau :", - "NetworkInterfaceTooltip": "Interface réseau pour les fonctionnalités LAN", + "NetworkInterfaceTooltip": "L'interface réseau utilisée pour les fonctionnalités LAN/LDN.\n\nEn conjonction avec un VPN ou XLink Kai et un jeu prenant en charge le LAN, peut être utilisée pour simuler une connexion sur le même réseau via Internet.\n\nLaissez sur DEFAULT si vous n'êtes pas sûr.", "NetworkInterfaceDefault": "Par défaut", "PackagingShaders": "Empaquetage des Shaders", "AboutChangelogButton": "Voir le Changelog sur GitHub", - "AboutChangelogButtonTooltipMessage": "Cliquez pour ouvrir le changelog de cette version dans votre navigateur par défaut." -} \ No newline at end of file + "AboutChangelogButtonTooltipMessage": "Cliquez pour ouvrir le changelog de cette version dans votre navigateur par défaut.", + "SettingsTabNetworkMultiplayer": "Multijoueur", + "MultiplayerMode": "Mode :", + "MultiplayerModeTooltip": "Changer le mode multijoueur LDN.\n\nLdnMitm modifiera la fonctionnalité de jeu sans fil local/jeu local dans les jeux pour fonctionner comme s'il s'agissait d'un LAN, permettant des connexions locales sur le même réseau avec d'autres instances de Ryujinx et des consoles Nintendo Switch piratées ayant le module ldn_mitm installé.\n\nLe multijoueur nécessite que tous les joueurs soient sur la même version du jeu (par exemple, Super Smash Bros. Ultimate v13.0.1 ne peut pas se connecter à v13.0.0).\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr." +} diff --git a/src/Ryujinx/Assets/Locales/he_IL.json b/src/Ryujinx/Assets/Locales/he_IL.json index e5caf445a7..5ba56c2691 100644 --- a/src/Ryujinx/Assets/Locales/he_IL.json +++ b/src/Ryujinx/Assets/Locales/he_IL.json @@ -1,5 +1,5 @@ { - "Language": "אנגלית (ארה\"ב)", + "Language": "עִברִית", "MenuBarFileOpenApplet": "פתח יישומון", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "פתח את יישומון עורך ה- Mii במצב עצמאי", "SettingsTabInputDirectMouseAccess": "גישה ישירה לעכבר", @@ -14,7 +14,7 @@ "MenuBarFileOpenEmuFolder": "פתח את תיקיית ריוג'ינקס", "MenuBarFileOpenLogsFolder": "פתח את תיקיית קבצי הלוג", "MenuBarFileExit": "_יציאה", - "MenuBarOptions": "הגדרות", + "MenuBarOptions": "_אפשרויות", "MenuBarOptionsToggleFullscreen": "שנה מצב- מסך מלא", "MenuBarOptionsStartGamesInFullscreen": "התחל משחקים במסך מלא", "MenuBarOptionsStopEmulation": "עצור אמולציה", @@ -30,7 +30,7 @@ "MenuBarToolsManageFileTypes": "ניהול סוגי קבצים", "MenuBarToolsInstallFileTypes": "סוגי קבצי התקנה", "MenuBarToolsUninstallFileTypes": "סוגי קבצי הסרה", - "MenuBarHelp": "עזרה", + "MenuBarHelp": "_עזרה", "MenuBarHelpCheckForUpdates": "חפש עדכונים", "MenuBarHelpAbout": "אודות", "MenuSearch": "חפש...", @@ -54,8 +54,6 @@ "GameListContextMenuManageTitleUpdatesToolTip": "פותח את חלון מנהל עדכוני המשחקים", "GameListContextMenuManageDlc": "מנהל הרחבות", "GameListContextMenuManageDlcToolTip": "פותח את חלון מנהל הרחבות המשחקים", - "GameListContextMenuOpenModsDirectory": "פתח את תקיית המודים", - "GameListContextMenuOpenModsDirectoryToolTip": "פותח את התקייה המכילה את המודים של היישום", "GameListContextMenuCacheManagement": "ניהול מטמון", "GameListContextMenuCacheManagementPurgePptc": "הוסף PPTC לתור בנייה מחדש", "GameListContextMenuCacheManagementPurgePptcToolTip": "גרום ל-PPTC להבנות מחדש בפתיחה הבאה של המשחק", @@ -72,6 +70,13 @@ "GameListContextMenuExtractDataRomFSToolTip": "חלץ את קטע ה-RomFS מתצורת היישום הנוכחית (כולל עדכונים)", "GameListContextMenuExtractDataLogo": "Logo", "GameListContextMenuExtractDataLogoToolTip": "חלץ את קטע ה-Logo מתצורת היישום הנוכחית (כולל עדכונים)", + "GameListContextMenuCreateShortcut": "ליצור קיצור דרך לאפליקציה", + "GameListContextMenuCreateShortcutToolTip": "ליצור קיצור דרך בשולחן העבודה שיפתח את אפליקציה זו", + "GameListContextMenuCreateShortcutToolTipMacOS": "ליצור קיצור דרך בתיקיית האפליקציות של macOS שיפתח את אפליקציה זו", + "GameListContextMenuOpenModsDirectory": "פתח תיקיית מודים", + "GameListContextMenuOpenModsDirectoryToolTip": "פותח את התיקייה שמכילה מודים של האפליקציה", + "GameListContextMenuOpenSdModsDirectory": "פתח תיקיית מודים של Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "פותח את תיקיית כרטיס ה-SD החלופית של Atmosphere המכילה את המודים של האפליקציה. שימושי עבור מודים שארוזים עבור חומרה אמיתית.", "StatusBarGamesLoaded": "{1}/{0} משחקים נטענו", "StatusBarSystemVersion": "גרסת מערכת: {0}", "LinuxVmMaxMapCountDialogTitle": "זוהתה מגבלה נמוכה עבור מיפויי זיכרון", @@ -150,7 +155,7 @@ "SettingsTabGraphicsResolutionScaleNative": "מקורי (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Not recommended)", "SettingsTabGraphicsAspectRatio": "יחס גובה-רוחב:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -292,13 +297,9 @@ "GameListContextMenuRunApplication": "הרץ יישום", "GameListContextMenuToggleFavorite": "למתג העדפה", "GameListContextMenuToggleFavoriteToolTip": "למתג סטטוס העדפה של משחק", - "SettingsTabGeneralTheme": "ערכת נושא", - "SettingsTabGeneralThemeCustomTheme": "נתיב ערכת נושא מותאמת אישית", - "SettingsTabGeneralThemeBaseStyle": "סגנון בסיס", - "SettingsTabGeneralThemeBaseStyleDark": "כהה", - "SettingsTabGeneralThemeBaseStyleLight": "בהיר", - "SettingsTabGeneralThemeEnableCustomTheme": "אפשר ערכת נושא מותאמת אישית", - "ButtonBrowse": "עיין", + "SettingsTabGeneralTheme": "ערכת נושא:", + "SettingsTabGeneralThemeDark": "כהה", + "SettingsTabGeneralThemeLight": "בהיר", "ControllerSettingsConfigureGeneral": "הגדר", "ControllerSettingsRumble": "רטט", "ControllerSettingsRumbleStrongMultiplier": "העצמת רטט חזק", @@ -332,8 +333,6 @@ "DialogUpdaterAddingFilesMessage": "מוסיף עדכון חדש...", "DialogUpdaterCompleteMessage": "העדכון הושלם!", "DialogUpdaterRestartMessage": "האם אתם רוצים להפעיל מחדש את ריוג'ינקס עכשיו?", - "DialogUpdaterArchNotSupportedMessage": "אינך מריץ ארכיטקטורת מערכת נתמכת!", - "DialogUpdaterArchNotSupportedSubMessage": "(רק מערכות x64 נתמכות!)", "DialogUpdaterNoInternetMessage": "אתם לא מחוברים לאינטרנט!", "DialogUpdaterNoInternetSubMessage": "אנא ודא שיש לך חיבור אינטרנט תקין!", "DialogUpdaterDirtyBuildMessage": "אתם לא יכולים לעדכן מבנה מלוכלך של ריוג'ינקס!", @@ -385,7 +384,10 @@ "DialogUserProfileUnsavedChangesSubMessage": "האם ברצונך למחוק את השינויים האחרונים?", "DialogControllerSettingsModifiedConfirmMessage": "הגדרות השלט הנוכחי עודכנו.", "DialogControllerSettingsModifiedConfirmSubMessage": "האם ברצונך לשמור?", - "DialogLoadNcaErrorMessage": "{0}. קובץ שגוי: {1}", + "DialogLoadFileErrorMessage": "{0}. קובץ שגוי: {1}", + "DialogModAlreadyExistsMessage": "מוד כבר קיים", + "DialogModInvalidMessage": "התיקייה שצוינה אינה מכילה מוד", + "DialogModDeleteNoParentMessage": "נכשל למחוק: לא היה ניתן למצוא את תיקיית האב למוד \"{0}\"!\n", "DialogDlcNoDlcErrorMessage": "הקובץ שצוין אינו מכיל DLC עבור המשחק שנבחר!", "DialogPerformanceCheckLoggingEnabledMessage": "הפעלת רישום מעקב, אשר נועד לשמש מפתחים בלבד.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "לביצועים מיטביים, מומלץ להשבית את רישום המעקב. האם ברצונך להשבית את רישום המעקב כעת?", @@ -396,6 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "הקובץ שצוין אינו מכיל עדכון עבור המשחק שנבחר!", "DialogSettingsBackendThreadingWarningTitle": "אזהרה - ריבוי תהליכי רקע", "DialogSettingsBackendThreadingWarningMessage": "יש להפעיל מחדש את ריוג'ינקס לאחר שינוי אפשרות זו כדי שהיא תחול במלואה. בהתאם לפלטפורמה שלך, ייתכן שיהיה עליך להשבית ידנית את ריבוי ההליכים של ההתקן שלך בעת השימוש ב-ריוג'ינקס.", + "DialogModManagerDeletionWarningMessage": "אתה עומד למחוק את המוד: {0}\nהאם אתה בטוח שאתה רוצה להמשיך?", + "DialogModManagerDeletionAllWarningMessage": "אתה עומד למחוק את כל המודים בשביל משחק זה.\n\nהאם אתה בטוח שאתה רוצה להמשיך?", "SettingsTabGraphicsFeaturesOptions": "אפשרויות", "SettingsTabGraphicsBackendMultithreading": "אחראי גרפיקה רב-תהליכי:", "CommonAuto": "אוטומטי", @@ -430,6 +434,7 @@ "DlcManagerRemoveAllButton": "מחק הכל", "DlcManagerEnableAllButton": "אפשר הכל", "DlcManagerDisableAllButton": "השבת הכל", + "ModManagerDeleteAllButton": "מחק הכל", "MenuBarOptionsChangeLanguage": "החלף שפה", "MenuBarShowFileTypes": "הצג מזהה סוג קובץ", "CommonSort": "מיין", @@ -447,13 +452,13 @@ "CustomThemePathTooltip": "נתיב לערכת נושא לממשק גראפי מותאם אישית", "CustomThemeBrowseTooltip": "חפש עיצוב ממשק גראפי מותאם אישית", "DockModeToggleTooltip": "מצב עגינה גורם למערכת המדומה להתנהג כ-נינטנדו סוויץ' בתחנת עגינתו. זה משפר את הנאמנות הגרפית ברוב המשחקים.\n לעומת זאת, השבתה של תכונה זו תגרום למערכת המדומה להתנהג כ- נינטנדו סוויץ' נייד, ולהפחית את איכות הגרפיקה.\n\nהגדירו את שלט שחקן 1 אם אתם מתכננים להשתמש במצב עגינה; הגדירו את פקדי כף היד אם אתם מתכננים להשתמש במצב נייד.\n\nמוטב להשאיר דלוק אם אתם לא בטוחים.", - "DirectKeyboardTooltip": "תמיכה ישירה למקלדת (HID). מספק גישה בשביל משחקים למקלדת שלך כמכשיר להזנת טקסט.", - "DirectMouseTooltip": "תמיכה ישירה לעכבר (HID). מספק גישה בשביל משחקים לעכבר שלך כהתקן הצבעה.", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", "RegionTooltip": "שנה אזור מערכת", "LanguageTooltip": "שנה שפת מערכת", "TimezoneTooltip": "שנה את אזור הזמן של המערכת", "TimeTooltip": "שנה זמן מערכת", - "VSyncToggleTooltip": "מדמה סנכרון אנכי של קונסולה. כלומר חסם הפריימים לרוב המשחקים; השבתה שלו עלולה לגרום למשחקים לרוץ מהר יותר או לגרום למסכי טעינה לקחת יותר זמן או להתקע.\n\nניתן לשנות מצב של תפריט זה בזמן משחק עם המקש לבחירתך. אנו ממליצים לעשות זאת אם אתם מתכננים להשבית אותו.\n\nמוטב להשאיר דלוק אם לא בטוחים.", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", "PptcToggleTooltip": "שומר את פונקציות ה-JIT המתורגמות כך שלא יצטרכו לעבור תרגום שוב כאשר משחק עולה.\n\nמפחית תקיעות ומשפר מהירות עלייה של המערכת אחרי הפתיחה הראשונה של המשחק.\n\nמוטב להשאיר דלוק אם לא בטוחים.", "FsIntegrityToggleTooltip": "בודק לקבצים שגויים כאשר משחק עולה, ואם מתגלים כאלו, מציג את מזהה השגיאה שלהם לקובץ הלוג.\n\nאין לכך השפעה על הביצועים ונועד לעזור לבדיקה וניפוי שגיאות של האמולטור.\n\nמוטב להשאיר דלוק אם לא בטוחים.", "AudioBackendTooltip": "משנה את אחראי השמע.\n\nSDL2 הוא הנבחר, למראת שOpenAL וגם SoundIO משומשים כאפשרויות חלופיות. אפשרות הDummy לא תשמיע קול כלל.\n\nמוטב להשאיר על SDL2 אם לא בטוחים.", @@ -467,10 +472,10 @@ "GraphicsBackendThreadingTooltip": "מריץ פקודות גראפיקה בתהליך שני נפרד.\n\nמאיץ עיבוד הצללות, מפחית תקיעות ומשפר ביצועים של דרייבר כרטיסי מסך אשר לא תומכים בהרצה רב-תהליכית.\n\nמוטב להשאיר על אוטומטי אם לא בטוחים.", "GalThreadingTooltip": "מריץ פקודות גראפיקה בתהליך שני נפרד.\n\nמאיץ עיבוד הצללות, מפחית תקיעות ומשפר ביצועים של דרייבר כרטיסי מסך אשר לא תומכים בהרצה רב-תהליכית.\n\nמוטב להשאיר על אוטומטי אם לא בטוחים.", "ShaderCacheToggleTooltip": "שומר זכרון מטמון של הצללות, דבר שמפחית תקיעות בריצות מסוימות.\n\nמוטב להשאיר דלוק אם לא בטוחים.", - "ResolutionScaleTooltip": "שיפור רזולוצייה המאופשרת לעיבוד מטרות.", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", "ResolutionScaleEntryTooltip": "שיפור רזולוציית נקודה צפה, כגון 1.5. הוא שיפור לא אינטגרלי הנוטה לגרום יותר בעיות או להקריס.", - "AnisotropyTooltip": "רמת סינון אניסוטרופי (מוגדר לאוטומטי כדי להשתמש בערך המבוקש על ידי המשחק)", - "AspectRatioTooltip": "יחס גובה-רוחב הוחל על חלון המעבד.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", "ShaderDumpPathTooltip": "נתיב השלכת הצללות גראפיות", "FileLogTooltip": "שומר את רישומי שורת הפקודות לזיכרון, לא משפיע על ביצועי היישום.", "StubLogTooltip": "מדפיס רישומים כושלים לשורת הפקודות. לא משפיע על ביצועי היישום.", @@ -504,6 +509,8 @@ "EnableInternetAccessTooltip": "מאפשר ליישומים באמולצייה להתחבר לאינטרנט.\n\nמשחקים עם חיבור לאן יכולים להתחבר אחד לשני כשאופצייה זו מופעלת והמערכות מתחברות לאותה נקודת גישה. כמו כן זה כולל שורות פקודות אמיתיות גם.\n\nאופצייה זו לא מאפשרת חיבור לשרתי נינטנדו. כשהאופצייה דלוקה היא עלולה לגרום לקריסת היישום במשחקים מסויימים שמנסים להתחבר לאינטרנט.\n\nמוטב להשאיר כבוי אם לא בטוחים.", "GameListContextMenuManageCheatToolTip": "נהל צ'יטים", "GameListContextMenuManageCheat": "נהל צ'יטים", + "GameListContextMenuManageModToolTip": "נהל מודים", + "GameListContextMenuManageMod": "נהל מודים", "ControllerSettingsStickRange": "טווח:", "DialogStopEmulationTitle": "ריוג'ינקס - עצור אמולציה", "DialogStopEmulationMessage": "האם אתם בטוחים שאתם רוצים לסגור את האמולצייה?", @@ -515,8 +522,6 @@ "SettingsTabCpuMemory": "מצב מעבד", "DialogUpdaterFlatpakNotSupportedMessage": "בבקשה עדכן את ריוג'ינקס דרך פלאטהב.", "UpdaterDisabledWarningTitle": "מעדכן מושבת!", - "GameListContextMenuOpenSdModsDirectory": "פתח את תקיית המודים של אטמוספרה", - "GameListContextMenuOpenSdModsDirectoryToolTip": "פותח את תקיית כרטיס ה-SD החלופית של אטמוספרה המכילה את המודים של האפליקציה. שימושי עבור מודים ארוזים עבור חומרה אמיתית.", "ControllerSettingsRotate90": "סובב 90° עם בכיוון השעון", "IconSize": "גודל הסמל", "IconSizeTooltip": "שנה את גודל הסמלים של משחקים", @@ -544,12 +549,13 @@ "SwkbdMinCharacters": "לפחות {0} תווים", "SwkbdMinRangeCharacters": "באורך {0}-{1} תווים", "SoftwareKeyboard": "מקלדת וירטואלית", - "SoftwareKeyboardModeNumbersOnly": "מחויב להיות מספרי בלבד", + "SoftwareKeyboardModeNumeric": "חייב להיות בין 0-9 או '.' בלבד", "SoftwareKeyboardModeAlphabet": "מחויב להיות ללא אותיות CJK", "SoftwareKeyboardModeASCII": "מחויב להיות טקסט אסקיי", - "DialogControllerAppletMessagePlayerRange": "האפליקציה מבקשת {0} שחקנים עם:\n\nסוגים: {1}\n\nשחקנים: {2}\n\n{3}אנא פתח את ההגדרות והגדר מחדש את הקלט כעת או סגור.", - "DialogControllerAppletMessage": "האפליקציה מבקשת בדיוק {0} שחקנים עם:\n\nסוגים: {1}\n\nשחקנים: {2}\n\n{3}אנא פתח את ההגדרות והגדר מחדש את הקלט כעת או סגור.", - "DialogControllerAppletDockModeSet": "במצב עגינה. בנוסף מצב נייד לא אפשרי.\n\n", + "ControllerAppletControllers": "בקרים נתמכים:", + "ControllerAppletPlayers": "שחקנים:", + "ControllerAppletDescription": "התצורה הנוכחית אינה תקינה. פתח הגדרות והגדר מחדש את הקלטים שלך.", + "ControllerAppletDocked": "מצב עגינה מוגדר. כדאי ששליטה ניידת תהיה מושבתת.", "UpdaterRenaming": "משנה שמות של קבצים ישנים...", "UpdaterRenameFailed": "המעדכן לא הצליח לשנות את שם הקובץ: {0}", "UpdaterAddingFiles": "מוסיף קבצים חדשים...", @@ -587,6 +593,7 @@ "Writable": "ניתן לכתיבה", "SelectDlcDialogTitle": "בחרו קבצי הרחבות משחק", "SelectUpdateDialogTitle": "בחרו קבצי עדכון", + "SelectModDialogTitle": "בחר תיקיית מודים", "UserProfileWindowTitle": "ניהול פרופילי משתמש", "CheatWindowTitle": "נהל צ'יטים למשחק", "DlcWindowTitle": "נהל הרחבות משחק עבור {0} ({1})", @@ -594,10 +601,12 @@ "CheatWindowHeading": "צ'יטים זמינים עבור {0} [{1}]", "BuildId": "מזהה בניה:", "DlcWindowHeading": "{0} הרחבות משחק", + "ModWindowHeading": "{0} מוד(ים)", "UserProfilesEditProfile": "ערוך נבחר/ים", "Cancel": "בטל", "Save": "שמור", "Discard": "השלך", + "Paused": "מושהה", "UserProfilesSetProfileImage": "הגדר תמונת פרופיל", "UserProfileEmptyNameError": "נדרש שם", "UserProfileNoImageError": "נדרשת תמונת פרופיל", @@ -607,9 +616,9 @@ "UserProfilesName": "שם:", "UserProfilesUserId": "מזהה משתמש:", "SettingsTabGraphicsBackend": "אחראי גראפיקה", - "SettingsTabGraphicsBackendTooltip": "אחראי גראפיקה לשימוש", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", "SettingsEnableTextureRecompression": "אפשר דחיסה מחדש של המרקם", - "SettingsEnableTextureRecompressionTooltip": " דוחס מרקמים מסויימים להפחתת השימוש בראם הוירטואלי.\n\nמומלץ לשימוש עם כרטיס גראפי בעל פחות מ-4GiB בראם הוירטואלי.\n\nמוטב להשאיר כבוי אם אינכם בטוחים.", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", "SettingsTabGraphicsPreferredGpu": "כרטיס גראפי מועדף", "SettingsTabGraphicsPreferredGpuTooltip": "בחר את הכרטיס הגראפי שישומש עם הגראפיקה של וולקאן.\n\nדבר זה לא משפיע על הכרטיס הגראפי שישומש עם OpenGL.\n\nמוטב לבחור את ה-GPU המסומן כ-\"dGPU\" אם אינכם בטוחים, אם זו לא אופצייה, אל תשנו דבר.", "SettingsAppRequiredRestartMessage": "ריוג'ינקס דורש אתחול מחדש", @@ -620,8 +629,8 @@ "SettingsTabHotkeysVolumeDownHotkey": "הנמך את עוצמת הקול:", "SettingsEnableMacroHLE": "Enable Macro HLE", "SettingsEnableMacroHLETooltip": "אמולצייה ברמה גבוהה של כרטיס גראפי עם קוד מקרו.\n\nמשפר את ביצועי היישום אך עלול לגרום לגליצ'ים חזותיים במשחקים מסויימים.\n\nמוטב להשאיר דלוק אם אינך בטוח.", - "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", - "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", + "SettingsEnableColorSpacePassthrough": "שקיפות מרחב צבע", + "SettingsEnableColorSpacePassthroughTooltip": "מנחה את המנוע Vulkan להעביר שקיפות בצבעים מבלי לציין מרחב צבע. עבור משתמשים עם מסכים רחבים, הדבר עשוי לגרום לצבעים מרהיבים יותר, בחוסר דיוק בצבעים האמתיים.", "VolumeShort": "שמע", "UserProfilesManageSaves": "נהל שמורים", "DeleteUserSave": "האם ברצונך למחוק את תקיית השמור למשחק זה?", @@ -635,12 +644,12 @@ "Recover": "שחזר", "UserProfilesRecoverHeading": "שמורים נמצאו לחשבונות הבאים", "UserProfilesRecoverEmptyList": "אין פרופילים לשחזור", - "GraphicsAATooltip": "מחיל החלקת-עקומות על עיבוד המשחק", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", "GraphicsAALabel": "החלקת-עקומות:", "GraphicsScalingFilterLabel": "מסנן מידת איכות:", - "GraphicsScalingFilterTooltip": "אפשר מידת איכות מסוג איגור-תמונה", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", "GraphicsScalingFilterLevelLabel": "רמה", - "GraphicsScalingFilterLevelTooltip": "קבע מידת איכות תמונה לפי רמת סינון", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "SMAA נמוך", "SmaaMedium": "SMAA בינוני", "SmaaHigh": "SMAA גבוהה", @@ -648,9 +657,12 @@ "UserEditorTitle": "ערוך משתמש", "UserEditorTitleCreate": "צור משתמש", "SettingsTabNetworkInterface": "ממשק רשת", - "NetworkInterfaceTooltip": "ממשק הרשת המשומש עבור יכולות לאן", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", "NetworkInterfaceDefault": "ברירת המחדל", "PackagingShaders": "אורז הצללות", "AboutChangelogButton": "צפה במידע אודות שינויים בגיטהב", - "AboutChangelogButtonTooltipMessage": "לחץ כדי לפתוח את יומן השינויים עבור גרסה זו בדפדפן ברירת המחדל שלך." -} \ No newline at end of file + "AboutChangelogButtonTooltipMessage": "לחץ כדי לפתוח את יומן השינויים עבור גרסה זו בדפדפן ברירת המחדל שלך.", + "SettingsTabNetworkMultiplayer": "רב משתתפים", + "MultiplayerMode": "מצב:", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." +} diff --git a/src/Ryujinx/Assets/Locales/it_IT.json b/src/Ryujinx/Assets/Locales/it_IT.json index 5aff7a7ec3..7db45b0015 100644 --- a/src/Ryujinx/Assets/Locales/it_IT.json +++ b/src/Ryujinx/Assets/Locales/it_IT.json @@ -14,7 +14,7 @@ "MenuBarFileOpenEmuFolder": "Apri cartella di Ryujinx", "MenuBarFileOpenLogsFolder": "Apri cartella dei Log", "MenuBarFileExit": "_Esci", - "MenuBarOptions": "Opzioni", + "MenuBarOptions": "_Opzioni", "MenuBarOptionsToggleFullscreen": "Schermo intero", "MenuBarOptionsStartGamesInFullscreen": "Avvia i giochi a schermo intero", "MenuBarOptionsStopEmulation": "Ferma emulazione", @@ -30,7 +30,7 @@ "MenuBarToolsManageFileTypes": "Gestisci i tipi di file", "MenuBarToolsInstallFileTypes": "Installa i tipi di file", "MenuBarToolsUninstallFileTypes": "Disinstalla i tipi di file", - "MenuBarHelp": "Aiuto", + "MenuBarHelp": "_Aiuto", "MenuBarHelpCheckForUpdates": "Controlla aggiornamenti", "MenuBarHelpAbout": "Informazioni", "MenuSearch": "Cerca...", @@ -54,8 +54,6 @@ "GameListContextMenuManageTitleUpdatesToolTip": "Apre la finestra di gestione aggiornamenti del gioco", "GameListContextMenuManageDlc": "Gestici DLC", "GameListContextMenuManageDlcToolTip": "Apre la finestra di gestione DLC", - "GameListContextMenuOpenModsDirectory": "Apri cartella delle mod", - "GameListContextMenuOpenModsDirectoryToolTip": "Apre la cartella che contiene le mod dell'applicazione", "GameListContextMenuCacheManagement": "Gestione della cache", "GameListContextMenuCacheManagementPurgePptc": "Pulisci PPTC cache", "GameListContextMenuCacheManagementPurgePptcToolTip": "Elimina la PPTC cache dell'applicazione", @@ -72,6 +70,13 @@ "GameListContextMenuExtractDataRomFSToolTip": "Estrae la sezione RomFS dall'attuale configurazione dell'applicazione (includendo aggiornamenti)", "GameListContextMenuExtractDataLogo": "Logo", "GameListContextMenuExtractDataLogoToolTip": "Estrae la sezione Logo dall'attuale configurazione dell'applicazione (includendo aggiornamenti)", + "GameListContextMenuCreateShortcut": "Crea Collegamento", + "GameListContextMenuCreateShortcutToolTip": "Crea un collegamento del gioco sul Desktop", + "GameListContextMenuCreateShortcutToolTipMacOS": "Crea un collegamento del gioco sulla Scrivania", + "GameListContextMenuOpenModsDirectory": "Apri la cartella delle Mods", + "GameListContextMenuOpenModsDirectoryToolTip": "Apre la cartella delle Mods del gioco", + "GameListContextMenuOpenSdModsDirectory": "Apri la cartella delle Mods Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Apre la cartella delle Mods Atmosphere (Utilizzare solo per le Mods eseguite su Hardware reale)", "StatusBarGamesLoaded": "{0}/{1} Giochi caricati", "StatusBarSystemVersion": "Versione di sistema: {0}", "LinuxVmMaxMapCountDialogTitle": "Limite basso per le mappature di memoria rilevate", @@ -150,7 +155,7 @@ "SettingsTabGraphicsResolutionScaleNative": "Nativa (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Non consigliato)", "SettingsTabGraphicsAspectRatio": "Rapporto d'aspetto:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -292,13 +297,9 @@ "GameListContextMenuRunApplication": "Esegui applicazione", "GameListContextMenuToggleFavorite": "Preferito", "GameListContextMenuToggleFavoriteToolTip": "Segna il gioco come preferito", - "SettingsTabGeneralTheme": "Tema", - "SettingsTabGeneralThemeCustomTheme": "Percorso del tema personalizzato", - "SettingsTabGeneralThemeBaseStyle": "Modalità", - "SettingsTabGeneralThemeBaseStyleDark": "Scura", - "SettingsTabGeneralThemeBaseStyleLight": "Chiara", - "SettingsTabGeneralThemeEnableCustomTheme": "Attiva tema personalizzato", - "ButtonBrowse": "Sfoglia", + "SettingsTabGeneralTheme": "Tema:", + "SettingsTabGeneralThemeDark": "Scuro", + "SettingsTabGeneralThemeLight": "Chiaro", "ControllerSettingsConfigureGeneral": "Configura", "ControllerSettingsRumble": "Vibrazione", "ControllerSettingsRumbleStrongMultiplier": "Moltiplicatore vibrazione forte", @@ -332,8 +333,6 @@ "DialogUpdaterAddingFilesMessage": "Aggiunta del nuovo aggiornamento...", "DialogUpdaterCompleteMessage": "Aggiornamento completato!", "DialogUpdaterRestartMessage": "Vuoi riavviare Ryujinx adesso?", - "DialogUpdaterArchNotSupportedMessage": "Non stai usando un'architettura di sistema supportata!", - "DialogUpdaterArchNotSupportedSubMessage": "(Solo sistemi x64 sono supportati!)", "DialogUpdaterNoInternetMessage": "Non sei connesso ad Internet!", "DialogUpdaterNoInternetSubMessage": "Verifica di avere una connessione ad Internet funzionante!", "DialogUpdaterDirtyBuildMessage": "Non puoi aggiornare una Dirty build di Ryujinx!", @@ -385,7 +384,10 @@ "DialogUserProfileUnsavedChangesSubMessage": "Vuoi scartare le modifiche?", "DialogControllerSettingsModifiedConfirmMessage": "Le attuali impostazioni del controller sono state aggiornate.", "DialogControllerSettingsModifiedConfirmSubMessage": "Vuoi salvare?", - "DialogLoadNcaErrorMessage": "{0}. File errato: {1}", + "DialogLoadFileErrorMessage": "{0}. Errore File: {1}", + "DialogModAlreadyExistsMessage": "La Mod risulta già installata", + "DialogModInvalidMessage": "Questa cartella non contiene nessuna Mods!", + "DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!", "DialogDlcNoDlcErrorMessage": "Il file specificato non contiene un DLC per il titolo selezionato!", "DialogPerformanceCheckLoggingEnabledMessage": "Hai abilitato il trace logging, che è progettato per essere usato solo dagli sviluppatori.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Per prestazioni ottimali, si raccomanda di disabilitare il trace logging. Vuoi disabilitare il debug logging adesso?", @@ -396,6 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "Il file specificato non contiene un aggiornamento per il titolo selezionato!", "DialogSettingsBackendThreadingWarningTitle": "Avviso - Backend Threading", "DialogSettingsBackendThreadingWarningMessage": "Ryujinx deve essere riavviato dopo aver cambiato questa opzione per applicarla completamente. A seconda della tua piattaforma, potrebbe essere necessario disabilitare manualmente il multithreading del driver quando usi quello di Ryujinx.", + "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", "SettingsTabGraphicsFeaturesOptions": "Funzionalità", "SettingsTabGraphicsBackendMultithreading": "Graphics Backend Multithreading", "CommonAuto": "Auto", @@ -430,6 +434,7 @@ "DlcManagerRemoveAllButton": "Rimuovi tutti", "DlcManagerEnableAllButton": "Abilita tutto", "DlcManagerDisableAllButton": "Disabilita tutto", + "ModManagerDeleteAllButton": "Delete All", "MenuBarOptionsChangeLanguage": "Cambia lingua", "MenuBarShowFileTypes": "Mostra tipi di file", "CommonSort": "Ordina", @@ -447,13 +452,13 @@ "CustomThemePathTooltip": "Percorso al tema GUI personalizzato", "CustomThemeBrowseTooltip": "Sfoglia per cercare un tema GUI personalizzato", "DockModeToggleTooltip": "Attiva o disabilta modalità TV", - "DirectKeyboardTooltip": "Attiva o disattiva \"il supporto all'accesso diretto alla tastiera (HID)\" (Fornisce l'accesso ai giochi alla tua tastiera come dispositivo di immissione del testo)", - "DirectMouseTooltip": "Attiva o disattiva \"il supporto all'accesso diretto al mouse (HID)\" (Fornisce l'accesso ai giochi al tuo mouse come dispositivo di puntamento)", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", "RegionTooltip": "Cambia regione di sistema", "LanguageTooltip": "Cambia lingua di sistema", "TimezoneTooltip": "Cambia fuso orario di sistema", "TimeTooltip": "Cambia data e ora di sistema", - "VSyncToggleTooltip": "Attiva o disattiva sincronizzazione verticale", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", "PptcToggleTooltip": "Attiva o disattiva PPTC", "FsIntegrityToggleTooltip": "Attiva controlli d'integrità sui file dei contenuti di gioco", "AudioBackendTooltip": "Cambia backend audio", @@ -467,10 +472,10 @@ "GraphicsBackendThreadingTooltip": "Attiva il Graphics Backend Multithreading", "GalThreadingTooltip": "Esegue i comandi del backend grafico su un secondo thread. Permette il multithreading runtime della compilazione degli shader, riduce lo stuttering e migliora le prestazioni sui driver senza supporto multithreading proprio. Varia leggermente le prestazioni di picco sui driver con multithreading. Ryujinx potrebbe aver bisogno di essere riavviato per disabilitare correttamente il multithreading integrato nel driver, o potrebbe essere necessario farlo manualmente per ottenere le migliori prestazioni.", "ShaderCacheToggleTooltip": "Attiva o disattiva la Shader Cache", - "ResolutionScaleTooltip": "Scala della risoluzione applicata ai render targets applicabili", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", "ResolutionScaleEntryTooltip": "Scala della risoluzione in virgola mobile, come 1,5. Le scale non integrali hanno maggiori probabilità di causare problemi o crash.", - "AnisotropyTooltip": "Livello del filtro anisotropico (imposta su Auto per usare il valore richiesto dal gioco)", - "AspectRatioTooltip": "Rapporto d'aspetto applicato alla finestra del renderer.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", "ShaderDumpPathTooltip": "Percorso di dump Graphics Shaders", "FileLogTooltip": "Attiva o disattiva il logging su file", "StubLogTooltip": "Attiva messaggi stub log", @@ -504,6 +509,8 @@ "EnableInternetAccessTooltip": "Attiva il guest Internet access. Se abilitato, l'applicazione si comporterà come se la console Switch emulata fosse collegata a Internet. Si noti che in alcuni casi, le applicazioni possono comunque accedere a Internet anche con questa opzione disabilitata", "GameListContextMenuManageCheatToolTip": "Gestisci Cheats", "GameListContextMenuManageCheat": "Gestisci Cheats", + "GameListContextMenuManageModToolTip": "Manage Mods", + "GameListContextMenuManageMod": "Manage Mods", "ControllerSettingsStickRange": "Raggio:", "DialogStopEmulationTitle": "Ryujinx - Ferma emulazione", "DialogStopEmulationMessage": "Sei sicuro di voler fermare l'emulazione?", @@ -515,8 +522,6 @@ "SettingsTabCpuMemory": "Memoria CPU", "DialogUpdaterFlatpakNotSupportedMessage": "Per favore aggiorna Ryujinx via FlatHub.", "UpdaterDisabledWarningTitle": "Updater disabilitato!", - "GameListContextMenuOpenSdModsDirectory": "Apri cartella delle mods Atmosphere", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Apre la cartella Atmosphere della scheda SD alternativa che contiene le Mod dell'applicazione. Utile per mod confezionate per hardware reale", "ControllerSettingsRotate90": "Ruota in senso orario di 90°", "IconSize": "Dimensioni icona", "IconSizeTooltip": "Cambia le dimensioni dell'icona di un gioco", @@ -544,12 +549,13 @@ "SwkbdMinCharacters": "Non può avere meno di {0} caratteri", "SwkbdMinRangeCharacters": "Può avere da {0} a {1} caratteri", "SoftwareKeyboard": "Tastiera software", - "SoftwareKeyboardModeNumbersOnly": "Deve essere solo numeri", + "SoftwareKeyboardModeNumeric": "Must be 0-9 or '.' only", "SoftwareKeyboardModeAlphabet": "Deve essere solo caratteri non CJK", "SoftwareKeyboardModeASCII": "Deve essere solo testo ASCII", - "DialogControllerAppletMessagePlayerRange": "L'applicazione richiede {0} giocatori con:\n\nTIPI: {1}\n\nGIOCATORI: {2}\n\n{3}Apri le impostazioni e riconfigura l'input adesso o premi Chiudi.", - "DialogControllerAppletMessage": "L'applicazione richiede esattamente {0} giocatori con:\n\nTIPI: {1}\n\nGIOCATORI: {2}\n\n{3}Apri le impostazioni e riconfigura l'input adesso o premi Chiudi.", - "DialogControllerAppletDockModeSet": "Modalità TV attivata. Neanche portatile è valida.\n\n", + "ControllerAppletControllers": "Supported Controllers:", + "ControllerAppletPlayers": "Players:", + "ControllerAppletDescription": "Your current configuration is invalid. Open settings and reconfigure your inputs.", + "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.", "UpdaterRenaming": "Rinominazione dei vecchi files...", "UpdaterRenameFailed": "L'updater non è riuscito a rinominare il file: {0}", "UpdaterAddingFiles": "Aggiunta nuovi files...", @@ -587,6 +593,7 @@ "Writable": "Scrivibile", "SelectDlcDialogTitle": "Seleziona file dei DLC", "SelectUpdateDialogTitle": "Seleziona file di aggiornamento", + "SelectModDialogTitle": "Select mod directory", "UserProfileWindowTitle": "Gestisci profili degli utenti", "CheatWindowTitle": "Gestisci cheat dei giochi", "DlcWindowTitle": "Gestisci DLC dei giochi", @@ -594,10 +601,12 @@ "CheatWindowHeading": "Cheat disponibiili per {0} [{1}]", "BuildId": "ID Build", "DlcWindowHeading": "DLC disponibili per {0} [{1}]", + "ModWindowHeading": "{0} Mod(s)", "UserProfilesEditProfile": "Modifica selezionati", "Cancel": "Annulla", "Save": "Salva", "Discard": "Scarta", + "Paused": "Paused", "UserProfilesSetProfileImage": "Imposta immagine profilo", "UserProfileEmptyNameError": "È richiesto un nome", "UserProfileNoImageError": "Dev'essere impostata un'immagine profilo", @@ -607,9 +616,9 @@ "UserProfilesName": "Nome:", "UserProfilesUserId": "ID utente:", "SettingsTabGraphicsBackend": "Backend grafica", - "SettingsTabGraphicsBackendTooltip": "Backend grafica da usare", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", "SettingsEnableTextureRecompression": "Abilita Ricompressione Texture", - "SettingsEnableTextureRecompressionTooltip": "Comprime alcune texture per ridurre l'utilizzo della VRAM.\n\nL'utilizzo è consigliato con GPU con meno di 4GB di VRAM.\n\nLascia su OFF se non sei sicuro.", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", "SettingsTabGraphicsPreferredGpu": "GPU preferita", "SettingsTabGraphicsPreferredGpuTooltip": "Seleziona la scheda grafica che verrà usata con la backend grafica Vulkan.\n\nNon influenza la GPU che userà OpenGL.\n\nImposta la GPU contrassegnata come \"dGPU\" se non sei sicuro. Se non ce n'è una, lascia intatta quest'impostazione.", "SettingsAppRequiredRestartMessage": "È richiesto un riavvio di Ryujinx", @@ -635,12 +644,12 @@ "Recover": "Recupera", "UserProfilesRecoverHeading": "Sono stati trovati dei salvataggi per i seguenti account", "UserProfilesRecoverEmptyList": "Nessun profilo da recuperare", - "GraphicsAATooltip": "Applica anti-aliasing al rendering del gioco", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", "GraphicsAALabel": "Anti-Aliasing:", "GraphicsScalingFilterLabel": "Filtro di scala:", - "GraphicsScalingFilterTooltip": "Abilita scalatura Framebuffer", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", "GraphicsScalingFilterLevelLabel": "Livello", - "GraphicsScalingFilterLevelTooltip": "Imposta livello del filtro di scala", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "SMAA Basso", "SmaaMedium": "SMAA Medio", "SmaaHigh": "SMAA Alto", @@ -648,9 +657,12 @@ "UserEditorTitle": "Modificare L'Utente", "UserEditorTitleCreate": "Crea Un Utente", "SettingsTabNetworkInterface": "Interfaccia di rete:", - "NetworkInterfaceTooltip": "L'interfaccia di rete utilizzata per le funzionalità LAN", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", "NetworkInterfaceDefault": "Predefinito", "PackagingShaders": "Comprimendo shader", "AboutChangelogButton": "Visualizza changelog su GitHub", - "AboutChangelogButtonTooltipMessage": "Clicca per aprire il changelog per questa versione nel tuo browser predefinito." -} \ No newline at end of file + "AboutChangelogButtonTooltipMessage": "Clicca per aprire il changelog per questa versione nel tuo browser predefinito.", + "SettingsTabNetworkMultiplayer": "Multiplayer", + "MultiplayerMode": "Mode:", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." +} diff --git a/src/Ryujinx/Assets/Locales/ja_JP.json b/src/Ryujinx/Assets/Locales/ja_JP.json index 5b31c5f206..ce4f07c415 100644 --- a/src/Ryujinx/Assets/Locales/ja_JP.json +++ b/src/Ryujinx/Assets/Locales/ja_JP.json @@ -1,5 +1,5 @@ { - "Language": "英語 (アメリカ)", + "Language": "日本語", "MenuBarFileOpenApplet": "アプレットを開く", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "スタンドアロンモードで Mii エディタアプレットを開きます", "SettingsTabInputDirectMouseAccess": "マウス直接アクセス", @@ -14,7 +14,7 @@ "MenuBarFileOpenEmuFolder": "Ryujinx フォルダを開く", "MenuBarFileOpenLogsFolder": "ログフォルダを開く", "MenuBarFileExit": "終了(_E)", - "MenuBarOptions": "オプション", + "MenuBarOptions": "オプション(_O)", "MenuBarOptionsToggleFullscreen": "全画面切り替え", "MenuBarOptionsStartGamesInFullscreen": "全画面モードでゲームを開始", "MenuBarOptionsStopEmulation": "エミュレーションを停止", @@ -30,7 +30,7 @@ "MenuBarToolsManageFileTypes": "ファイル形式を管理", "MenuBarToolsInstallFileTypes": "ファイル形式をインストール", "MenuBarToolsUninstallFileTypes": "ファイル形式をアンインストール", - "MenuBarHelp": "ヘルプ", + "MenuBarHelp": "ヘルプ(_H)", "MenuBarHelpCheckForUpdates": "アップデートを確認", "MenuBarHelpAbout": "Ryujinx について", "MenuSearch": "検索...", @@ -54,8 +54,6 @@ "GameListContextMenuManageTitleUpdatesToolTip": "タイトルのアップデート管理ウインドウを開きます", "GameListContextMenuManageDlc": "DLCを管理", "GameListContextMenuManageDlcToolTip": "DLC管理ウインドウを開きます", - "GameListContextMenuOpenModsDirectory": "Modディレクトリを開く", - "GameListContextMenuOpenModsDirectoryToolTip": "アプリケーションの Mod データを格納するディレクトリを開きます", "GameListContextMenuCacheManagement": "キャッシュ管理", "GameListContextMenuCacheManagementPurgePptc": "PPTC を再構築", "GameListContextMenuCacheManagementPurgePptcToolTip": "次回のゲーム起動時に PPTC を再構築します", @@ -72,6 +70,13 @@ "GameListContextMenuExtractDataRomFSToolTip": "現在のアプリケーション設定(アップデート含む)から RomFS セクションを展開します", "GameListContextMenuExtractDataLogo": "ロゴ", "GameListContextMenuExtractDataLogoToolTip": "現在のアプリケーション設定(アップデート含む)からロゴセクションを展開します", + "GameListContextMenuCreateShortcut": "アプリケーションのショートカットを作成", + "GameListContextMenuCreateShortcutToolTip": "選択したアプリケーションを起動するデスクトップショートカットを作成します", + "GameListContextMenuCreateShortcutToolTipMacOS": "Create a shortcut in macOS's Applications folder that launches the selected Application", + "GameListContextMenuOpenModsDirectory": "Modディレクトリを開く", + "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", + "GameListContextMenuOpenSdModsDirectory": "Atmosphere Mods ディレクトリを開く", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", "StatusBarGamesLoaded": "{0}/{1} ゲーム", "StatusBarSystemVersion": "システムバージョン: {0}", "LinuxVmMaxMapCountDialogTitle": "メモリマッピング上限値が小さすぎます", @@ -150,7 +155,7 @@ "SettingsTabGraphicsResolutionScaleNative": "ネイティブ (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (非推奨)", "SettingsTabGraphicsAspectRatio": "アスペクト比:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -292,13 +297,9 @@ "GameListContextMenuRunApplication": "アプリケーションを実行", "GameListContextMenuToggleFavorite": "お気に入りを切り替え", "GameListContextMenuToggleFavoriteToolTip": "ゲームをお気に入りに含めるかどうかを切り替えます", - "SettingsTabGeneralTheme": "テーマ", - "SettingsTabGeneralThemeCustomTheme": "カスタムテーマパス", - "SettingsTabGeneralThemeBaseStyle": "基本スタイル", - "SettingsTabGeneralThemeBaseStyleDark": "ダーク", - "SettingsTabGeneralThemeBaseStyleLight": "ライト", - "SettingsTabGeneralThemeEnableCustomTheme": "カスタムテーマを有効にする", - "ButtonBrowse": "参照", + "SettingsTabGeneralTheme": "テーマ:", + "SettingsTabGeneralThemeDark": "ダーク", + "SettingsTabGeneralThemeLight": "ライト", "ControllerSettingsConfigureGeneral": "設定", "ControllerSettingsRumble": "振動", "ControllerSettingsRumbleStrongMultiplier": "強振動の補正値", @@ -332,8 +333,6 @@ "DialogUpdaterAddingFilesMessage": "新規アップデートを追加中...", "DialogUpdaterCompleteMessage": "アップデート完了!", "DialogUpdaterRestartMessage": "すぐに Ryujinx を再起動しますか?", - "DialogUpdaterArchNotSupportedMessage": "サポート外のアーキテクチャです!", - "DialogUpdaterArchNotSupportedSubMessage": "(x64 システムのみサポートしています!)", "DialogUpdaterNoInternetMessage": "インターネットに接続されていません!", "DialogUpdaterNoInternetSubMessage": "インターネット接続が正常動作しているか確認してください!", "DialogUpdaterDirtyBuildMessage": "Dirty ビルドの Ryujinx はアップデートできません!", @@ -385,7 +384,10 @@ "DialogUserProfileUnsavedChangesSubMessage": "変更を破棄しますか?", "DialogControllerSettingsModifiedConfirmMessage": "現在のコントローラ設定が更新されました.", "DialogControllerSettingsModifiedConfirmSubMessage": "セーブしますか?", - "DialogLoadNcaErrorMessage": "{0}. エラー発生ファイル: {1}", + "DialogLoadFileErrorMessage": "{0}. エラー発生ファイル: {1}", + "DialogModAlreadyExistsMessage": "Mod already exists", + "DialogModInvalidMessage": "The specified directory does not contain a mod!", + "DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!", "DialogDlcNoDlcErrorMessage": "選択されたファイルはこのタイトル用の DLC ではありません!", "DialogPerformanceCheckLoggingEnabledMessage": "トレースロギングを有効にします. これは開発者のみに有用な機能です.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "パフォーマンス最適化のためには,トレースロギングを無効にすることを推奨します. トレースロギングを無効にしてよろしいですか?", @@ -396,6 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "選択されたファイルはこのタイトル用のアップデートではありません!", "DialogSettingsBackendThreadingWarningTitle": "警告 - バックエンドスレッディング", "DialogSettingsBackendThreadingWarningMessage": "このオプションの変更を完全に適用するには Ryujinx の再起動が必要です. プラットフォームによっては, Ryujinx のものを使用する前に手動でドライバ自身のマルチスレッディングを無効にする必要があるかもしれません.", + "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", "SettingsTabGraphicsFeaturesOptions": "機能", "SettingsTabGraphicsBackendMultithreading": "グラフィックスバックエンドのマルチスレッド実行:", "CommonAuto": "自動", @@ -430,6 +434,7 @@ "DlcManagerRemoveAllButton": "すべて削除", "DlcManagerEnableAllButton": "すべて有効", "DlcManagerDisableAllButton": "すべて無効", + "ModManagerDeleteAllButton": "すべて削除", "MenuBarOptionsChangeLanguage": "言語を変更", "MenuBarShowFileTypes": "ファイル形式を表示", "CommonSort": "並べ替え", @@ -447,13 +452,13 @@ "CustomThemePathTooltip": "カスタム GUI テーマのパスです", "CustomThemeBrowseTooltip": "カスタム GUI テーマを参照します", "DockModeToggleTooltip": "有効にすると,ドッキングされた Nintendo Switch をエミュレートします.多くのゲームではグラフィックス品質が向上します.\n無効にすると,携帯モードの Nintendo Switch をエミュレートします.グラフィックスの品質は低下します.\n\nドッキングモード有効ならプレイヤー1の,無効なら携帯の入力を設定してください.\n\nよくわからない場合はオンのままにしてください.", - "DirectKeyboardTooltip": "キーボード直接アクセス (HID) に対応します. キーボードをテキスト入力デバイスとして使用できます.", - "DirectMouseTooltip": "マウス直接アクセス (HID) に対応します. マウスをポインティングデバイスとして使用できます.", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", "RegionTooltip": "システムの地域を変更します", "LanguageTooltip": "システムの言語を変更します", "TimezoneTooltip": "システムのタイムゾーンを変更します", "TimeTooltip": "システムの時刻を変更します", - "VSyncToggleTooltip": "エミュレートされたゲーム機の垂直同期です. 多くのゲームにおいて, フレームリミッタとして機能します. 無効にすると, ゲームが高速で実行されたり, ロード中に時間がかかったり, 止まったりすることがあります.\n\n設定したホットキーで, ゲーム内で切り替え可能です. 無効にする場合は, この操作を行うことをおすすめします.\n\nよくわからない場合はオンのままにしてください.", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", "PptcToggleTooltip": "翻訳されたJIT関数をセーブすることで, ゲームをロードするたびに毎回翻訳する処理を不要とします.\n\n一度ゲームを起動すれば,二度目以降の起動時遅延を大きく軽減できます.\n\nよくわからない場合はオンのままにしてください.", "FsIntegrityToggleTooltip": "ゲーム起動時にファイル破損をチェックし,破損が検出されたらログにハッシュエラーを表示します..\n\nパフォーマンスには影響なく, トラブルシューティングに役立ちます.\n\nよくわからない場合はオンのままにしてください.", "AudioBackendTooltip": "音声レンダリングに使用するバックエンドを変更します.\n\nSDL2 が優先され, OpenAL と SoundIO はフォールバックとして使用されます. ダミーは音声出力しません.\n\nよくわからない場合は SDL2 を設定してください.", @@ -467,10 +472,10 @@ "GraphicsBackendThreadingTooltip": "グラフィックスバックエンドのコマンドを別スレッドで実行します.\n\nシェーダのコンパイルを高速化し, 遅延を軽減し, マルチスレッド非対応の GPU ドライバにおいてパフォーマンスを改善します. マルチスレッド対応のドライバでも若干パフォーマンス改善が見られます.\n\nよくわからない場合は自動に設定してください.", "GalThreadingTooltip": "グラフィックスバックエンドのコマンドを別スレッドで実行します.\n\nシェーダのコンパイルを高速化し, 遅延を軽減し, マルチスレッド非対応の GPU ドライバにおいてパフォーマンスを改善します. マルチスレッド対応のドライバでも若干パフォーマンス改善が見られます.\n\nよくわからない場合は自動に設定してください.", "ShaderCacheToggleTooltip": "ディスクシェーダーキャッシュをセーブし,次回以降の実行時遅延を軽減します.\n\nよくわからない場合はオンのままにしてください.", - "ResolutionScaleTooltip": "レンダリングに適用される解像度の倍率です", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", "ResolutionScaleEntryTooltip": "1.5 のような整数でない倍率を指定すると,問題が発生したりクラッシュしたりする場合があります.", - "AnisotropyTooltip": "異方性フィルタリングのレベルです (ゲームが要求する値を使用する場合は「自動」を設定してください)", - "AspectRatioTooltip": "レンダリングに適用されるアスペクト比です.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", "ShaderDumpPathTooltip": "グラフィックス シェーダー ダンプのパスです", "FileLogTooltip": "コンソール出力されるログをディスク上のログファイルにセーブします. パフォーマンスには影響を与えません.", "StubLogTooltip": "stub ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", @@ -504,6 +509,8 @@ "EnableInternetAccessTooltip": "エミュレートしたアプリケーションをインターネットに接続できるようにします.\n\nLAN モードを持つゲーム同士は,この機能を有効にして同じアクセスポイントに接続すると接続できます. 実機も含まれます.\n\n任天堂のサーバーには接続できません. インターネットに接続しようとすると,特定のゲームでクラッシュすることがあります.\n\nよくわからない場合はオフのままにしてください.", "GameListContextMenuManageCheatToolTip": "チートを管理します", "GameListContextMenuManageCheat": "チートを管理", + "GameListContextMenuManageModToolTip": "Manage Mods", + "GameListContextMenuManageMod": "Manage Mods", "ControllerSettingsStickRange": "範囲:", "DialogStopEmulationTitle": "Ryujinx - エミュレーションを停止", "DialogStopEmulationMessage": "エミュレーションを停止してよろしいですか?", @@ -515,8 +522,6 @@ "SettingsTabCpuMemory": "CPU メモリ", "DialogUpdaterFlatpakNotSupportedMessage": "FlatHub を使用して Ryujinx をアップデートしてください.", "UpdaterDisabledWarningTitle": "アップデータは無効です!", - "GameListContextMenuOpenSdModsDirectory": "Atmosphere Mods ディレクトリを開く", - "GameListContextMenuOpenSdModsDirectoryToolTip": "アプリケーションの Mod データを格納する SD カードの Atmosphere ディレクトリを開きます. 実際のハードウェア用にパッケージされた Mod データに有用です.", "ControllerSettingsRotate90": "時計回りに 90° 回転", "IconSize": "アイコンサイズ", "IconSizeTooltip": "ゲームアイコンのサイズを変更します", @@ -544,12 +549,13 @@ "SwkbdMinCharacters": "最低 {0} 文字必要です", "SwkbdMinRangeCharacters": "{0}-{1} 文字にしてください", "SoftwareKeyboard": "ソフトウェアキーボード", - "SoftwareKeyboardModeNumbersOnly": "数字のみ", + "SoftwareKeyboardModeNumeric": "0-9 または '.' のみでなければなりません", "SoftwareKeyboardModeAlphabet": "CJK文字以外のみ", "SoftwareKeyboardModeASCII": "ASCII文字列のみ", - "DialogControllerAppletMessagePlayerRange": "アプリケーションは {0} 名のプレイヤーを要求しています:\n\n種別: {1}\n\nプレイヤー: {2}\n\n{3}設定を開き各プレイヤーの入力設定を行ってから閉じるを押してください.", - "DialogControllerAppletMessage": "アプリケーションは {0} 名のプレイヤーを要求しています:\n\n種別: {1}\n\nプレイヤー: {2}\n\n{3}設定を開き各プレイヤーの入力設定を行ってから閉じるを押してください.", - "DialogControllerAppletDockModeSet": "ドッキングモードに設定されました. 携帯モードは無効になります.\n\n", + "ControllerAppletControllers": "サポートされているコントローラ:", + "ControllerAppletPlayers": "プレイヤー:", + "ControllerAppletDescription": "Your current configuration is invalid. Open settings and reconfigure your inputs.", + "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.", "UpdaterRenaming": "古いファイルをリネーム中...", "UpdaterRenameFailed": "ファイルをリネームできませんでした: {0}", "UpdaterAddingFiles": "新規ファイルを追加中...", @@ -587,6 +593,7 @@ "Writable": "書き込み可能", "SelectDlcDialogTitle": "DLC ファイルを選択", "SelectUpdateDialogTitle": "アップデートファイルを選択", + "SelectModDialogTitle": "Select mod directory", "UserProfileWindowTitle": "ユーザプロファイルを管理", "CheatWindowTitle": "チート管理", "DlcWindowTitle": "DLC 管理", @@ -594,10 +601,12 @@ "CheatWindowHeading": "利用可能なチート {0} [{1}]", "BuildId": "ビルドID:", "DlcWindowHeading": "利用可能な DLC {0} [{1}]", + "ModWindowHeading": "{0} Mod(s)", "UserProfilesEditProfile": "編集", "Cancel": "キャンセル", "Save": "セーブ", "Discard": "破棄", + "Paused": "Paused", "UserProfilesSetProfileImage": "プロファイル画像を設定", "UserProfileEmptyNameError": "名称が必要です", "UserProfileNoImageError": "プロファイル画像が必要です", @@ -607,9 +616,9 @@ "UserProfilesName": "名称:", "UserProfilesUserId": "ユーザID:", "SettingsTabGraphicsBackend": "グラフィックスバックエンド", - "SettingsTabGraphicsBackendTooltip": "使用するグラフィックスバックエンドです", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", "SettingsEnableTextureRecompression": "テクスチャの再圧縮を有効にする", - "SettingsEnableTextureRecompressionTooltip": "VRAMの使用量を削減するためテクスチャを圧縮します.\n\nGPUのVRAMが4GiB未満の場合は使用を推奨します.\n\nよくわからない場合はオフのままにしてください.", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", "SettingsTabGraphicsPreferredGpu": "優先使用するGPU", "SettingsTabGraphicsPreferredGpuTooltip": "Vulkanグラフィックスバックエンドで使用されるグラフィックスカードを選択します.\n\nOpenGLが使用するGPUには影響しません.\n\n不明な場合は, \"dGPU\" としてフラグが立っているGPUに設定します. ない場合はそのままにします.", "SettingsAppRequiredRestartMessage": "Ryujinx の再起動が必要です", @@ -635,12 +644,12 @@ "Recover": "復旧", "UserProfilesRecoverHeading": "以下のアカウントのセーブデータが見つかりました", "UserProfilesRecoverEmptyList": "復元するプロファイルはありません", - "GraphicsAATooltip": "ゲームのレンダリングにアンチエイリアスを適用します", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", "GraphicsAALabel": "アンチエイリアス:", "GraphicsScalingFilterLabel": "スケーリングフィルタ:", - "GraphicsScalingFilterTooltip": "フレームバッファスケーリングを有効にします", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", "GraphicsScalingFilterLevelLabel": "レベル", - "GraphicsScalingFilterLevelTooltip": "スケーリングフィルタのレベルを設定", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "SMAA Low", "SmaaMedium": "SMAA Medium", "SmaaHigh": "SMAA High", @@ -648,9 +657,12 @@ "UserEditorTitle": "ユーザを編集", "UserEditorTitleCreate": "ユーザを作成", "SettingsTabNetworkInterface": "ネットワークインタフェース:", - "NetworkInterfaceTooltip": "LAN機能に使用されるネットワークインタフェース", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", "NetworkInterfaceDefault": "デフォルト", "PackagingShaders": "シェーダーを構築中", "AboutChangelogButton": "GitHub で更新履歴を表示", - "AboutChangelogButtonTooltipMessage": "クリックして, このバージョンの更新履歴をデフォルトのブラウザで開きます." -} \ No newline at end of file + "AboutChangelogButtonTooltipMessage": "クリックして, このバージョンの更新履歴をデフォルトのブラウザで開きます.", + "SettingsTabNetworkMultiplayer": "マルチプレイヤー", + "MultiplayerMode": "モード:", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." +} diff --git a/src/Ryujinx/Assets/Locales/ko_KR.json b/src/Ryujinx/Assets/Locales/ko_KR.json index cdc6172224..1db5215e57 100644 --- a/src/Ryujinx/Assets/Locales/ko_KR.json +++ b/src/Ryujinx/Assets/Locales/ko_KR.json @@ -14,7 +14,7 @@ "MenuBarFileOpenEmuFolder": "Ryujinx 폴더 열기", "MenuBarFileOpenLogsFolder": "로그 폴더 열기", "MenuBarFileExit": "_종료", - "MenuBarOptions": "옵션", + "MenuBarOptions": "옵션(_O)", "MenuBarOptionsToggleFullscreen": "전체화면 전환", "MenuBarOptionsStartGamesInFullscreen": "전체 화면 모드에서 게임 시작", "MenuBarOptionsStopEmulation": "에뮬레이션 중지", @@ -30,7 +30,7 @@ "MenuBarToolsManageFileTypes": "파일 형식 관리", "MenuBarToolsInstallFileTypes": "파일 형식 설치", "MenuBarToolsUninstallFileTypes": "파일 형식 설치 제거", - "MenuBarHelp": "도움말", + "MenuBarHelp": "도움말(_H)", "MenuBarHelpCheckForUpdates": "업데이트 확인", "MenuBarHelpAbout": "정보", "MenuSearch": "검색...", @@ -46,7 +46,7 @@ "GameListHeaderPath": "경로", "GameListContextMenuOpenUserSaveDirectory": "사용자 저장 디렉터리 열기", "GameListContextMenuOpenUserSaveDirectoryToolTip": "응용프로그램의 사용자 저장이 포함된 디렉터리 열기", - "GameListContextMenuOpenDeviceSaveDirectory": "사용자 장치 디렉터리 열기", + "GameListContextMenuOpenDeviceSaveDirectory": "사용자 장치 디렉토리 열기", "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "응용프로그램의 장치 저장이 포함된 디렉터리 열기", "GameListContextMenuOpenBcatSaveDirectory": "BCAT 저장 디렉터리 열기", "GameListContextMenuOpenBcatSaveDirectoryToolTip": "응용프로그램의 BCAT 저장이 포함된 디렉터리 열기", @@ -54,8 +54,6 @@ "GameListContextMenuManageTitleUpdatesToolTip": "타이틀 업데이트 관리 창 열기", "GameListContextMenuManageDlc": "DLC 관리", "GameListContextMenuManageDlcToolTip": "DLC 관리 창 열기", - "GameListContextMenuOpenModsDirectory": "Mod 디렉터리 열기", - "GameListContextMenuOpenModsDirectoryToolTip": "응용프로그램의 Mod가 포함된 디렉터리 열기", "GameListContextMenuCacheManagement": "캐시 관리", "GameListContextMenuCacheManagementPurgePptc": "대기열 PPTC 재구성", "GameListContextMenuCacheManagementPurgePptcToolTip": "다음 게임 시작에서 부팅 시 PPTC가 다시 빌드하도록 트리거", @@ -72,6 +70,13 @@ "GameListContextMenuExtractDataRomFSToolTip": "응용 프로그램의 현재 구성에서 RomFS 추출 (업데이트 포함)", "GameListContextMenuExtractDataLogo": "로고", "GameListContextMenuExtractDataLogoToolTip": "응용프로그램의 현재 구성에서 로고 섹션 추출 (업데이트 포함)", + "GameListContextMenuCreateShortcut": "애플리케이션 바로 가기 만들기", + "GameListContextMenuCreateShortcutToolTip": "선택한 애플리케이션을 실행하는 바탕 화면 바로 가기를 만듭니다.", + "GameListContextMenuCreateShortcutToolTipMacOS": "Create a shortcut in macOS's Applications folder that launches the selected Application", + "GameListContextMenuOpenModsDirectory": "Open Mods Directory", + "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", + "GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", "StatusBarGamesLoaded": "{0}/{1}개의 게임 불러옴", "StatusBarSystemVersion": "시스템 버전 : {0}", "LinuxVmMaxMapCountDialogTitle": "감지된 메모리 매핑의 하한선", @@ -150,7 +155,7 @@ "SettingsTabGraphicsResolutionScaleNative": "원본(720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2배(1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3배(2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4배(2880p/4320p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Not recommended)", "SettingsTabGraphicsAspectRatio": "종횡비 :", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -292,13 +297,9 @@ "GameListContextMenuRunApplication": "응용프로그램 실행", "GameListContextMenuToggleFavorite": "즐겨찾기 전환", "GameListContextMenuToggleFavoriteToolTip": "게임 즐겨찾기 상태 전환", - "SettingsTabGeneralTheme": "테마", - "SettingsTabGeneralThemeCustomTheme": "커스텀 테마 경로", - "SettingsTabGeneralThemeBaseStyle": "기본 스타일", - "SettingsTabGeneralThemeBaseStyleDark": "어두움", - "SettingsTabGeneralThemeBaseStyleLight": "밝음", - "SettingsTabGeneralThemeEnableCustomTheme": "사용자 정의 테마 활성화", - "ButtonBrowse": "찾아보기", + "SettingsTabGeneralTheme": "Theme:", + "SettingsTabGeneralThemeDark": "Dark", + "SettingsTabGeneralThemeLight": "Light", "ControllerSettingsConfigureGeneral": "구성", "ControllerSettingsRumble": "진동", "ControllerSettingsRumbleStrongMultiplier": "강력한 진동 증폭기", @@ -332,8 +333,6 @@ "DialogUpdaterAddingFilesMessage": "새 업데이트 추가 중...", "DialogUpdaterCompleteMessage": "업데이트를 완료했습니다!", "DialogUpdaterRestartMessage": "지금 Ryujinx를 다시 시작하겠습니까?", - "DialogUpdaterArchNotSupportedMessage": "지원되는 시스템 아키텍처를 실행하고 있지 않습니다!", - "DialogUpdaterArchNotSupportedSubMessage": "(64비트 시스템만 지원됩니다!)", "DialogUpdaterNoInternetMessage": "인터넷에 연결되어 있지 않습니다!", "DialogUpdaterNoInternetSubMessage": "인터넷 연결이 작동하는지 확인하세요!", "DialogUpdaterDirtyBuildMessage": "Ryujinx의 나쁜 빌드는 업데이트할 수 없습니다!\n", @@ -385,7 +384,10 @@ "DialogUserProfileUnsavedChangesSubMessage": "변경사항을 저장하지 않으시겠습니까?", "DialogControllerSettingsModifiedConfirmMessage": "현재 컨트롤러 설정이 업데이트되었습니다.", "DialogControllerSettingsModifiedConfirmSubMessage": "저장하겠습니까?", - "DialogLoadNcaErrorMessage": "{0}. 오류 발생 파일 : {1}", + "DialogLoadFileErrorMessage": "{0}. Errored File: {1}", + "DialogModAlreadyExistsMessage": "Mod already exists", + "DialogModInvalidMessage": "The specified directory does not contain a mod!", + "DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!", "DialogDlcNoDlcErrorMessage": "지정된 파일에 선택한 타이틀에 대한 DLC가 포함되어 있지 않습니다!", "DialogPerformanceCheckLoggingEnabledMessage": "개발자만 사용하도록 설계된 추적 로그 기록이 활성화되어 있습니다.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "최적의 성능을 위해 추적 로그 생성을 비활성화하는 것이 좋습니다. 지금 추적 로그 기록을 비활성화하겠습니까?", @@ -396,6 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "지정된 파일에 선택한 제목에 대한 업데이트가 포함되어 있지 않습니다!", "DialogSettingsBackendThreadingWarningTitle": "경고 - 후단부 스레딩", "DialogSettingsBackendThreadingWarningMessage": "변경 사항을 완전히 적용하려면 이 옵션을 변경한 후, Ryujinx를 다시 시작해야 합니다. 플랫폼에 따라 Ryujinx를 사용할 때 드라이버 자체의 멀티스레딩을 수동으로 비활성화해야 할 수도 있습니다.", + "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", "SettingsTabGraphicsFeaturesOptions": "기능", "SettingsTabGraphicsBackendMultithreading": "그래픽 후단부 멀티스레딩 :", "CommonAuto": "자동", @@ -430,6 +434,7 @@ "DlcManagerRemoveAllButton": "모두 제거", "DlcManagerEnableAllButton": "모두 활성화", "DlcManagerDisableAllButton": "모두 비활성화", + "ModManagerDeleteAllButton": "Delete All", "MenuBarOptionsChangeLanguage": "언어 변경", "MenuBarShowFileTypes": "파일 유형 표시", "CommonSort": "정렬", @@ -447,13 +452,13 @@ "CustomThemePathTooltip": "사용자 정의 GUI 테마 경로", "CustomThemeBrowseTooltip": "사용자 정의 GUI 테마 찾아보기", "DockModeToggleTooltip": "독 모드에서는 에뮬레이트된 시스템이 도킹된 닌텐도 스위치처럼 작동합니다. 이것은 대부분의 게임에서 그래픽 품질을 향상시킵니다. 반대로 이 기능을 비활성화하면 에뮬레이트된 시스템이 휴대용 닌텐도 스위치처럼 작동하여 그래픽 품질이 저하됩니다.\n\n독 모드를 사용하려는 경우 플레이어 1의 컨트롤을 구성하세요. 휴대 모드를 사용하려는 경우 휴대용 컨트롤을 구성하세요.\n\n확실하지 않으면 켜 두세요.", - "DirectKeyboardTooltip": "직접 키보드 접속 (HID) 지원합니다. 텍스트 입력 장치로 키보드에 대한 게임 접속을 제공합니다.", - "DirectMouseTooltip": "직접 마우스 접속 (HID) 지원합니다. 포인팅 장치로 마우스에 대한 게임 접속을 제공합니다.", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", "RegionTooltip": "시스템 지역 변경", "LanguageTooltip": "시스템 언어 변경", "TimezoneTooltip": "시스템 시간대 변경", "TimeTooltip": "시스템 시간 변경", - "VSyncToggleTooltip": "에뮬레이트된 콘솔의 수직 동기화입니다. 기본적으로 대부분의 게임에 대한 프레임 제한 장치입니다. 비활성화하면 게임이 더 빠른 속도로 실행되거나 로딩 화면이 더 오래 걸리거나 멈출 수 있습니다.\n\n게임 내에서 선호하는 핫키로 전환할 수 있습니다. 비활성화할 계획이라면 이 작업을 수행하는 것이 좋습니다.\n\n확실하지 않으면 켜 두세요.", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", "PptcToggleTooltip": "게임이 불러올 때마다 번역할 필요가 없도록 번역된 JIT 기능을 저장합니다.\n\n게임을 처음 부팅한 후 끊김 현상을 줄이고 부팅 시간을 크게 단축합니다.\n\n확실하지 않으면 켜 두세요.", "FsIntegrityToggleTooltip": "게임을 부팅할 때 손상된 파일을 확인하고 손상된 파일이 감지되면 로그에 해시 오류를 표시합니다.\n\n성능에 영향을 미치지 않으며 문제 해결에 도움이 됩니다.\n\n확실하지 않으면 켜 두세요.", "AudioBackendTooltip": "오디오를 렌더링하는 데 사용되는 백엔드를 변경합니다.\n\nSDL2가 선호되는 반면 OpenAL 및 사운드IO는 폴백으로 사용됩니다. 더미는 소리가 나지 않습니다.\n\n확실하지 않으면 SDL2로 설정하세요.", @@ -467,10 +472,10 @@ "GraphicsBackendThreadingTooltip": "두 번째 스레드에서 그래픽 백엔드 명령을 실행합니다.\n\n세이더 컴파일 속도를 높이고 끊김 현상을 줄이며 자체 멀티스레딩 지원 없이 GPU 드라이버의 성능을 향상시킵니다. 멀티스레딩이 있는 드라이버에서 성능이 약간 향상되었습니다.\n\n잘 모르겠으면 자동으로 설정하세요.", "GalThreadingTooltip": "두 번째 스레드에서 그래픽 백엔드 명령을 실행합니다.\n\n세이더 컴파일 속도를 높이고 끊김 현상을 줄이며 자체 멀티스레딩 지원 없이 GPU 드라이버의 성능을 향상시킵니다. 멀티스레딩이 있는 드라이버에서 성능이 약간 향상되었습니다.\n\n잘 모르겠으면 자동으로 설정하세요.", "ShaderCacheToggleTooltip": "후속 실행에서 끊김 현상을 줄이는 디스크 세이더 캐시를 저장합니다.\n\n확실하지 않으면 켜 두세요.", - "ResolutionScaleTooltip": "적용 가능한 렌더 타겟에 적용된 해상도 스케일", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", "ResolutionScaleEntryTooltip": "1.5와 같은 부동 소수점 분해능 스케일입니다. 비통합 척도는 문제나 충돌을 일으킬 가능성이 더 큽니다.", - "AnisotropyTooltip": "비등방성 필터링 수준 (게임에서 요청한 값을 사용하려면 자동으로 설정)", - "AspectRatioTooltip": "렌더러 창에 적용된 화면비입니다.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", "ShaderDumpPathTooltip": "그래픽 셰이더 덤프 경로", "FileLogTooltip": "디스크의 로그 파일에 콘솔 로깅을 저장합니다. 성능에 영향을 미치지 않습니다.", "StubLogTooltip": "콘솔에 스텁 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", @@ -504,6 +509,8 @@ "EnableInternetAccessTooltip": "에뮬레이션된 응용프로그램이 인터넷에 연결되도록 허용합니다.\n\nLAN 모드가 있는 게임은 이 모드가 활성화되고 시스템이 동일한 접속 포인트에 연결된 경우 서로 연결할 수 있습니다. 여기에는 실제 콘솔도 포함됩니다.\n\n닌텐도 서버에 연결할 수 없습니다. 인터넷에 연결을 시도하는 특정 게임에서 충돌이 발생할 수 있습니다.\n\n확실하지 않으면 꺼두세요.", "GameListContextMenuManageCheatToolTip": "치트 관리", "GameListContextMenuManageCheat": "치트 관리", + "GameListContextMenuManageModToolTip": "Manage Mods", + "GameListContextMenuManageMod": "Manage Mods", "ControllerSettingsStickRange": "범위 :", "DialogStopEmulationTitle": "Ryujinx - 에뮬레이션 중지", "DialogStopEmulationMessage": "에뮬레이션을 중지하겠습니까?", @@ -515,8 +522,6 @@ "SettingsTabCpuMemory": "CPU 모드", "DialogUpdaterFlatpakNotSupportedMessage": "FlatHub를 통해 Ryujinx를 업데이트하세요.", "UpdaterDisabledWarningTitle": "업데이터 비활성화입니다!", - "GameListContextMenuOpenSdModsDirectory": "분위기 모드 디렉터리 열기", - "GameListContextMenuOpenSdModsDirectoryToolTip": "응용프로그램의 모드가 포함된 대체 SD 카드 분위기 디렉터리를 엽니다. 실제 하드웨어용으로 패키징된 모드에 유용합니다.", "ControllerSettingsRotate90": "시계 방향으로 90° 회전", "IconSize": "아이콘 크기", "IconSizeTooltip": "게임 아이콘 크기 변경", @@ -544,12 +549,13 @@ "SwkbdMinCharacters": "{0}자 이상이어야 함", "SwkbdMinRangeCharacters": "{0}-{1}자여야 함", "SoftwareKeyboard": "소프트웨어 키보드", - "SoftwareKeyboardModeNumbersOnly": "숫자만 가능", + "SoftwareKeyboardModeNumeric": "'0~9' 또는 '.'만 가능", "SoftwareKeyboardModeAlphabet": "한중일 문자가 아닌 문자만 가능", "SoftwareKeyboardModeASCII": "ASCII 텍스트만 가능", - "DialogControllerAppletMessagePlayerRange": "응용 프로그램은 다음을 사용하는 {0} 명의 플레이어를 요청합니다:\n\n유형: {1}\n\n플레이어: {2}\n\n{3} 지금 설정을 열고 입력을 재구성하거나 닫기를 누르세요.\n", - "DialogControllerAppletMessage": "응용 프로그램은 다음을 사용하는 정확히 {0}명의 플레이어를 요청합니다:\n\n유형: {1}\n\n플레이어: {2}\n\n{3} 지금 설정을 열고 입력을 재구성하거나 닫기를 누르세요.\n", - "DialogControllerAppletDockModeSet": "독 모드가 설정되었습니다. 휴대 모드도 유효하지 않습니다.", + "ControllerAppletControllers": "Supported Controllers:", + "ControllerAppletPlayers": "Players:", + "ControllerAppletDescription": "Your current configuration is invalid. Open settings and reconfigure your inputs.", + "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.", "UpdaterRenaming": "이전 파일 이름 바꾸는 중...", "UpdaterRenameFailed": "업데이터가 파일 이름을 바꿀 수 없음: {0}", "UpdaterAddingFiles": "새로운 파일을 추가하는 중...", @@ -587,6 +593,7 @@ "Writable": "쓰기 가능", "SelectDlcDialogTitle": "DLC 파일 선택", "SelectUpdateDialogTitle": "업데이트 파일 선택", + "SelectModDialogTitle": "Select mod directory", "UserProfileWindowTitle": "사용자 프로파일 관리자", "CheatWindowTitle": "치트 관리자", "DlcWindowTitle": "{0} ({1})의 다운로드 가능한 콘텐츠 관리", @@ -594,10 +601,12 @@ "CheatWindowHeading": "{0} [{1}]에 사용할 수 있는 치트", "BuildId": "빌드ID :", "DlcWindowHeading": "{0} 내려받기 가능한 콘텐츠", + "ModWindowHeading": "{0} Mod(s)", "UserProfilesEditProfile": "선택된 항목 편집", "Cancel": "취소", "Save": "저장", "Discard": "삭제", + "Paused": "일시 중지", "UserProfilesSetProfileImage": "프로파일 이미지 설정", "UserProfileEmptyNameError": "이름 필요", "UserProfileNoImageError": "프로파일 이미지를 설정해야 함", @@ -607,9 +616,9 @@ "UserProfilesName": "이름 :", "UserProfilesUserId": "사용자 ID :", "SettingsTabGraphicsBackend": "그래픽 후단부", - "SettingsTabGraphicsBackendTooltip": "사용할 그래픽 후단부", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", "SettingsEnableTextureRecompression": "텍스처 재압축 활성화", - "SettingsEnableTextureRecompressionTooltip": "VRAM 사용량을 줄이기 위해 특정 텍스처를 압축합니다.\n\n4GiB VRAM 미만의 GPU와 함께 사용하는 것이 좋습니다.\n\n확실하지 않으면 꺼 두세요.", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", "SettingsTabGraphicsPreferredGpu": "선호하는 GPU", "SettingsTabGraphicsPreferredGpuTooltip": "Vulkan 그래픽 후단부와 함께 사용할 그래픽 카드를 선택하세요.\n\nOpenGL이 사용할 GPU에는 영향을 미치지 않습니다.\n\n확실하지 않은 경우 \"dGPU\" 플래그가 지정된 GPU로 설정하세요. 없는 경우, 그대로 두세요.", "SettingsAppRequiredRestartMessage": "Ryujinx 다시 시작 필요", @@ -635,12 +644,12 @@ "Recover": "복구", "UserProfilesRecoverHeading": "다음 계정에 대한 저장 발견", "UserProfilesRecoverEmptyList": "복구할 프로파일이 없습니다", - "GraphicsAATooltip": "게임 렌더링에 안티 앨리어싱을 적용", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", "GraphicsAALabel": "안티 앨리어싱:", "GraphicsScalingFilterLabel": "스케일링 필터:", - "GraphicsScalingFilterTooltip": "프레임버퍼 스케일링 활성화", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", "GraphicsScalingFilterLevelLabel": "수준", - "GraphicsScalingFilterLevelTooltip": "스케일링 필터 수준 설정", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "SMAA 낮음", "SmaaMedium": "SMAA 중간", "SmaaHigh": "SMAA 높음", @@ -648,9 +657,12 @@ "UserEditorTitle": "사용자 수정", "UserEditorTitleCreate": "사용자 생성", "SettingsTabNetworkInterface": "네트워크 인터페이스:", - "NetworkInterfaceTooltip": "LAN 기능에 사용되는 네트워크 인터페이스", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", "NetworkInterfaceDefault": "기본", "PackagingShaders": "셰이더 패키징 중", "AboutChangelogButton": "GitHub에서 변경 로그 보기", - "AboutChangelogButtonTooltipMessage": "기본 브라우저에서 이 버전의 변경 로그를 열려면 클릭합니다." -} \ No newline at end of file + "AboutChangelogButtonTooltipMessage": "기본 브라우저에서 이 버전의 변경 로그를 열려면 클릭합니다.", + "SettingsTabNetworkMultiplayer": "멀티 플레이어", + "MultiplayerMode": "모드 :", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." +} diff --git a/src/Ryujinx/Assets/Locales/pl_PL.json b/src/Ryujinx/Assets/Locales/pl_PL.json index 7edf41e845..4647f4308d 100644 --- a/src/Ryujinx/Assets/Locales/pl_PL.json +++ b/src/Ryujinx/Assets/Locales/pl_PL.json @@ -1,112 +1,117 @@ { - "Language": "Angielski (USA)", + "Language": "Polski", "MenuBarFileOpenApplet": "Otwórz Aplet", - "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Otwórz aplet Mii Editor w trybie Indywidualnym", - "SettingsTabInputDirectMouseAccess": "Bezpośredni Dostęp do Myszy", - "SettingsTabSystemMemoryManagerMode": "Tryb Menedżera Pamięci:", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Otwórz aplet Mii Editor w trybie indywidualnym", + "SettingsTabInputDirectMouseAccess": "Bezpośredni dostęp do myszy", + "SettingsTabSystemMemoryManagerMode": "Tryb menedżera pamięci:", "SettingsTabSystemMemoryManagerModeSoftware": "Oprogramowanie", - "SettingsTabSystemMemoryManagerModeHost": "Host (szybko)", - "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host Niesprawdzony (najszybciej, niebezpiecznie)", - "SettingsTabSystemUseHypervisor": "Użyj Hiperwizora", + "SettingsTabSystemMemoryManagerModeHost": "Gospodarz (szybki)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Gospodarza (NIESPRAWDZONY, najszybszy, niebezpieczne)", + "SettingsTabSystemUseHypervisor": "Użyj Hipernadzorcy", "MenuBarFile": "_Plik", - "MenuBarFileOpenFromFile": "_Załaduj Aplikację z Pliku", - "MenuBarFileOpenUnpacked": "Załaduj _Rozpakowaną Grę", - "MenuBarFileOpenEmuFolder": "Otwórz Folder Ryujinx", - "MenuBarFileOpenLogsFolder": "Otwórz Folder Logów", + "MenuBarFileOpenFromFile": "_Załaduj aplikację z pliku", + "MenuBarFileOpenUnpacked": "Załaduj _rozpakowaną grę", + "MenuBarFileOpenEmuFolder": "Otwórz folder Ryujinx", + "MenuBarFileOpenLogsFolder": "Otwórz folder plików dziennika zdarzeń", "MenuBarFileExit": "_Wyjdź", - "MenuBarOptions": "Opcje", - "MenuBarOptionsToggleFullscreen": "Przełącz Tryb Pełnoekranowy", - "MenuBarOptionsStartGamesInFullscreen": "Uruchamiaj Gry w Trybie Pełnoekranowym", - "MenuBarOptionsStopEmulation": "Zatrzymaj Emulację", + "MenuBarOptions": "_Opcje", + "MenuBarOptionsToggleFullscreen": "Przełącz na tryb pełnoekranowy", + "MenuBarOptionsStartGamesInFullscreen": "Uruchamiaj gry w trybie pełnoekranowym", + "MenuBarOptionsStopEmulation": "Zatrzymaj emulację", "MenuBarOptionsSettings": "_Ustawienia", - "MenuBarOptionsManageUserProfiles": "_Zarządzaj Profilami Użytkowników", + "MenuBarOptionsManageUserProfiles": "_Zarządzaj profilami użytkowników", "MenuBarActions": "_Akcje", - "MenuBarOptionsSimulateWakeUpMessage": "Symuluj Wiadomość Budzenia", + "MenuBarOptionsSimulateWakeUpMessage": "Symuluj wiadomość wybudzania", "MenuBarActionsScanAmiibo": "Skanuj Amiibo", "MenuBarTools": "_Narzędzia", - "MenuBarToolsInstallFirmware": "Zainstaluj Firmware", - "MenuBarFileToolsInstallFirmwareFromFile": "Zainstaluj Firmware z XCI lub ZIP", - "MenuBarFileToolsInstallFirmwareFromDirectory": "Zainstaluj Firmware z Katalogu", - "MenuBarToolsManageFileTypes": "Zarządzaj rodzajem plików", - "MenuBarToolsInstallFileTypes": "Instalacja typów plików", - "MenuBarToolsUninstallFileTypes": "Odinstaluj rodzaje plików", - "MenuBarHelp": "Pomoc", - "MenuBarHelpCheckForUpdates": "Sprawdź Aktualizacje", - "MenuBarHelpAbout": "O Aplikacji", + "MenuBarToolsInstallFirmware": "Zainstaluj oprogramowanie", + "MenuBarFileToolsInstallFirmwareFromFile": "Zainstaluj oprogramowanie z XCI lub ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Zainstaluj oprogramowanie z katalogu", + "MenuBarToolsManageFileTypes": "Zarządzaj rodzajami plików", + "MenuBarToolsInstallFileTypes": "Typy plików instalacyjnych", + "MenuBarToolsUninstallFileTypes": "Typy plików dezinstalacyjnych", + "MenuBarHelp": "_Pomoc", + "MenuBarHelpCheckForUpdates": "Sprawdź aktualizacje", + "MenuBarHelpAbout": "O programie", "MenuSearch": "Wyszukaj...", - "GameListHeaderFavorite": "Ulub", + "GameListHeaderFavorite": "Ulubione", "GameListHeaderIcon": "Ikona", "GameListHeaderApplication": "Nazwa", - "GameListHeaderDeveloper": "Deweloper", + "GameListHeaderDeveloper": "Twórca", "GameListHeaderVersion": "Wersja", - "GameListHeaderTimePlayed": "Czas Gry", - "GameListHeaderLastPlayed": "Ostatnio Grane", - "GameListHeaderFileExtension": "Rozsz. Pliku", - "GameListHeaderFileSize": "Rozm. Pliku", + "GameListHeaderTimePlayed": "Czas w grze:", + "GameListHeaderLastPlayed": "Ostatnio grane", + "GameListHeaderFileExtension": "Rozszerzenie pliku", + "GameListHeaderFileSize": "Rozmiar pliku", "GameListHeaderPath": "Ścieżka", - "GameListContextMenuOpenUserSaveDirectory": "Otwórz Katalog Zapisów Użytkownika", - "GameListContextMenuOpenUserSaveDirectoryToolTip": "Otwiera katalog, który zawiera Zapis Użytkownika Aplikacji", - "GameListContextMenuOpenDeviceSaveDirectory": "Otwórz Katalog Urządzeń Użytkownika", - "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Otwiera katalog, który zawiera Zapis Urządzenia Aplikacji", - "GameListContextMenuOpenBcatSaveDirectory": "Otwórz Katalog BCAT Użytkownika", - "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Otwiera katalog, który zawiera Zapis BCAT Aplikacji", - "GameListContextMenuManageTitleUpdates": "Zarządzaj Aktualizacjami Tytułów", - "GameListContextMenuManageTitleUpdatesToolTip": "Otwiera okno zarządzania Aktualizacjami Tytułu", - "GameListContextMenuManageDlc": "Zarządzaj DLC", - "GameListContextMenuManageDlcToolTip": "Otwiera okno zarządzania DLC", - "GameListContextMenuOpenModsDirectory": "Otwórz Katalog Modów", - "GameListContextMenuOpenModsDirectoryToolTip": "Otwiera katalog zawierający Mody Aplikacji", + "GameListContextMenuOpenUserSaveDirectory": "Otwórz katalog zapisów użytkownika", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Otwiera katalog, który zawiera zapis użytkownika dla tej aplikacji", + "GameListContextMenuOpenDeviceSaveDirectory": "Otwórz katalog zapisów urządzenia", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Otwiera katalog, który zawiera zapis urządzenia dla tej aplikacji", + "GameListContextMenuOpenBcatSaveDirectory": "Otwórz katalog zapisu BCAT obecnego użytkownika", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Otwiera katalog, który zawiera zapis BCAT dla tej aplikacji", + "GameListContextMenuManageTitleUpdates": "Zarządzaj aktualizacjami", + "GameListContextMenuManageTitleUpdatesToolTip": "Otwiera okno zarządzania aktualizacjami danej aplikacji", + "GameListContextMenuManageDlc": "Zarządzaj dodatkową zawartością (DLC)", + "GameListContextMenuManageDlcToolTip": "Otwiera okno zarządzania dodatkową zawartością", "GameListContextMenuCacheManagement": "Zarządzanie Cache", - "GameListContextMenuCacheManagementPurgePptc": "Dodaj Rekompilację PPTC do Kolejki", + "GameListContextMenuCacheManagementPurgePptc": "Zakolejkuj rekompilację PPTC", "GameListContextMenuCacheManagementPurgePptcToolTip": "Zainicjuj Rekompilację PPTC przy następnym uruchomieniu gry", - "GameListContextMenuCacheManagementPurgeShaderCache": "Wyczyść Cache Shaderów", - "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Usuwa cache shaderów aplikacji", - "GameListContextMenuCacheManagementOpenPptcDirectory": "Otwórz Katalog PPTC", - "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Otwiera katalog, który zawiera cache PPTC aplikacji", - "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Otwórz Katalog Cache Shaderów", - "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Otwiera katalog, który zawiera cache shaderów aplikacji", - "GameListContextMenuExtractData": "Wyodrębnij Dane", + "GameListContextMenuCacheManagementPurgeShaderCache": "Wyczyść pamięć podręczną cieni", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Usuwa pamięć podręczną cieni danej aplikacji", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Otwórz katalog PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Otwiera katalog, który zawiera pamięć podręczną PPTC aplikacji", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Otwórz katalog pamięci podręcznej cieni", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Otwiera katalog, który zawiera pamięć podręczną cieni aplikacji", + "GameListContextMenuExtractData": "Wypakuj dane", "GameListContextMenuExtractDataExeFS": "ExeFS", "GameListContextMenuExtractDataExeFSToolTip": "Wyodrębnij sekcję ExeFS z bieżącej konfiguracji aplikacji (w tym aktualizacje)", "GameListContextMenuExtractDataRomFS": "RomFS", "GameListContextMenuExtractDataRomFSToolTip": "Wyodrębnij sekcję RomFS z bieżącej konfiguracji aplikacji (w tym aktualizacje)", "GameListContextMenuExtractDataLogo": "Logo", - "GameListContextMenuExtractDataLogoToolTip": "Wyodrębnij sekcję Logo z bieżącej konfiguracji aplikacji (w tym aktualizacje)", - "StatusBarGamesLoaded": "{0}/{1} Załadowane Gry", - "StatusBarSystemVersion": "Wersja Systemu: {0}", - "LinuxVmMaxMapCountDialogTitle": "Wykryto niski limit dla mapowania pamięci", - "LinuxVmMaxMapCountDialogTextPrimary": "Czy chciałbyś zwiększyć wartość vm.max_map_count do {0}", - "LinuxVmMaxMapCountDialogTextSecondary": "Niektóre gry mogą próbować stworzyć więcej mapowań pamięci niż obecnie, jest to dozwolone. Ryujinx napotka crash, gdy dojdzie do takiej sytuacji.", + "GameListContextMenuExtractDataLogoToolTip": "Wyodrębnij sekcję z logiem z bieżącej konfiguracji aplikacji (w tym aktualizacje)", + "GameListContextMenuCreateShortcut": "Utwórz skrót aplikacji", + "GameListContextMenuCreateShortcutToolTip": "Utwórz skrót na pulpicie, który uruchamia wybraną aplikację", + "GameListContextMenuCreateShortcutToolTipMacOS": "Utwórz skrót w folderze 'Aplikacje' w systemie macOS, który uruchamia wybraną aplikację", + "GameListContextMenuOpenModsDirectory": "Otwórz katalog modów", + "GameListContextMenuOpenModsDirectoryToolTip": "Otwiera katalog zawierający mody dla danej aplikacji", + "GameListContextMenuOpenSdModsDirectory": "Otwórz katalog modów Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Otwiera alternatywny katalog Atmosphere na karcie SD, który zawiera mody danej aplikacji. Przydatne dla modów przygotowanych pod prawdziwy sprzęt.", + "StatusBarGamesLoaded": "{0}/{1} Załadowane gry", + "StatusBarSystemVersion": "Wersja systemu: {0}", + "LinuxVmMaxMapCountDialogTitle": "Wykryto niski limit dla przypisań pamięci", + "LinuxVmMaxMapCountDialogTextPrimary": "Czy chcesz zwiększyć wartość vm.max_map_count do {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "Niektóre gry mogą próbować przypisać sobie więcej pamięci niż obecnie, jest to dozwolone. Ryujinx ulegnie awarii, gdy limit zostanie przekroczony.", "LinuxVmMaxMapCountDialogButtonUntilRestart": "Tak, do następnego ponownego uruchomienia", "LinuxVmMaxMapCountDialogButtonPersistent": "Tak, permanentnie ", - "LinuxVmMaxMapCountWarningTextPrimary": "Maksymalna ilość mapowania pamięci jest mniejsza niż zalecana.", + "LinuxVmMaxMapCountWarningTextPrimary": "Maksymalna ilość przypisanej pamięci jest mniejsza niż zalecana.", "LinuxVmMaxMapCountWarningTextSecondary": "Obecna wartość vm.max_map_count ({0}) jest mniejsza niż {1}. Niektóre gry mogą próbować stworzyć więcej mapowań pamięci niż obecnie jest to dozwolone. Ryujinx napotka crash, gdy dojdzie do takiej sytuacji.\n\nMożesz chcieć ręcznie zwiększyć limit lub zainstalować pkexec, co pozwala Ryujinx na pomoc w tym zakresie.", "Settings": "Ustawienia", - "SettingsTabGeneral": "Interfejs Użytkownika", + "SettingsTabGeneral": "Interfejs użytkownika", "SettingsTabGeneralGeneral": "Ogólne", "SettingsTabGeneralEnableDiscordRichPresence": "Włącz Bogatą Obecność Discord", - "SettingsTabGeneralCheckUpdatesOnLaunch": "Sprawdź Aktualizacje przy Uruchomieniu", - "SettingsTabGeneralShowConfirmExitDialog": "Pokaż Okno Dialogowe \"Potwierdzenia Wyjścia\"", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Sprawdzaj aktualizacje przy uruchomieniu", + "SettingsTabGeneralShowConfirmExitDialog": "Pokazuj okno dialogowe \"Potwierdź wyjście\"", "SettingsTabGeneralHideCursor": "Ukryj kursor:", "SettingsTabGeneralHideCursorNever": "Nigdy", - "SettingsTabGeneralHideCursorOnIdle": "Ukryj Kursor Podczas Bezczynności", + "SettingsTabGeneralHideCursorOnIdle": "Gdy bezczynny", "SettingsTabGeneralHideCursorAlways": "Zawsze", - "SettingsTabGeneralGameDirectories": "Katalogi Gier", + "SettingsTabGeneralGameDirectories": "Katalogi gier", "SettingsTabGeneralAdd": "Dodaj", "SettingsTabGeneralRemove": "Usuń", "SettingsTabSystem": "System", "SettingsTabSystemCore": "Główne", - "SettingsTabSystemSystemRegion": "Region Systemu:", + "SettingsTabSystemSystemRegion": "Region systemu:", "SettingsTabSystemSystemRegionJapan": "Japonia", - "SettingsTabSystemSystemRegionUSA": "USA", + "SettingsTabSystemSystemRegionUSA": "Stany Zjednoczone", "SettingsTabSystemSystemRegionEurope": "Europa", "SettingsTabSystemSystemRegionAustralia": "Australia", "SettingsTabSystemSystemRegionChina": "Chiny", "SettingsTabSystemSystemRegionKorea": "Korea", "SettingsTabSystemSystemRegionTaiwan": "Tajwan", - "SettingsTabSystemSystemLanguage": "Język Systemu:", + "SettingsTabSystemSystemLanguage": "Język systemu:", "SettingsTabSystemSystemLanguageJapanese": "Japoński", - "SettingsTabSystemSystemLanguageAmericanEnglish": "Amerykański Angielski", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Angielski (Stany Zjednoczone)", "SettingsTabSystemSystemLanguageFrench": "Francuski", "SettingsTabSystemSystemLanguageGerman": "Niemiecki", "SettingsTabSystemSystemLanguageItalian": "Włoski", @@ -117,16 +122,16 @@ "SettingsTabSystemSystemLanguagePortuguese": "Portugalski", "SettingsTabSystemSystemLanguageRussian": "Rosyjski", "SettingsTabSystemSystemLanguageTaiwanese": "Tajwański", - "SettingsTabSystemSystemLanguageBritishEnglish": "Brytyjski Angielski", + "SettingsTabSystemSystemLanguageBritishEnglish": "Angielski (Wielka Brytania)", "SettingsTabSystemSystemLanguageCanadianFrench": "Kanadyjski Francuski", - "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Hiszpański Latynoamerykański", - "SettingsTabSystemSystemLanguageSimplifiedChinese": "Chiński Uproszczony", - "SettingsTabSystemSystemLanguageTraditionalChinese": "Chiński Tradycyjny", - "SettingsTabSystemSystemTimeZone": "Strefa Czasowa Systemu:", - "SettingsTabSystemSystemTime": "Czas Systemu:", - "SettingsTabSystemEnableVsync": "Włącz synchronizację pionową", - "SettingsTabSystemEnablePptc": "PPTC (Profilowany Cache Trwałych Tłumaczeń)", - "SettingsTabSystemEnableFsIntegrityChecks": "Kontrole Integralności Systemu Plików", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Hiszpański (Ameryka Łacińska)", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Chiński (Uproszczony)", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Chiński (Tradycyjny)", + "SettingsTabSystemSystemTimeZone": "Strefa czasowa systemu:", + "SettingsTabSystemSystemTime": "Czas systemu:", + "SettingsTabSystemEnableVsync": "Synchronizacja pionowa", + "SettingsTabSystemEnablePptc": "PPTC (Profilowana pamięć podręczna trwałych łłumaczeń)", + "SettingsTabSystemEnableFsIntegrityChecks": "Sprawdzanie integralności systemu plików", "SettingsTabSystemAudioBackend": "Backend Dżwięku:", "SettingsTabSystemAudioBackendDummy": "Atrapa", "SettingsTabSystemAudioBackendOpenAL": "OpenAL", @@ -138,31 +143,31 @@ "SettingsTabSystemIgnoreMissingServices": "Ignoruj Brakujące Usługi", "SettingsTabGraphics": "Grafika", "SettingsTabGraphicsAPI": "Graficzne API", - "SettingsTabGraphicsEnableShaderCache": "Włącz Cache Shaderów", - "SettingsTabGraphicsAnisotropicFiltering": "Filtrowanie Anizotropowe:", - "SettingsTabGraphicsAnisotropicFilteringAuto": "Automatyczny", + "SettingsTabGraphicsEnableShaderCache": "Włącz pamięć podręczną cieni", + "SettingsTabGraphicsAnisotropicFiltering": "Filtrowanie anizotropowe:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Automatyczne", "SettingsTabGraphicsAnisotropicFiltering2x": "2x", "SettingsTabGraphicsAnisotropicFiltering4x": "4x", "SettingsTabGraphicsAnisotropicFiltering8x": "8x", "SettingsTabGraphicsAnisotropicFiltering16x": "16x", - "SettingsTabGraphicsResolutionScale": "Skala Rozdzielczości:", + "SettingsTabGraphicsResolutionScale": "Skalowanie rozdzielczości:", "SettingsTabGraphicsResolutionScaleCustom": "Niestandardowa (Niezalecane)", "SettingsTabGraphicsResolutionScaleNative": "Natywna (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", - "SettingsTabGraphicsAspectRatio": "Współczynnik Proporcji:", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (niezalecane)", + "SettingsTabGraphicsAspectRatio": "Format obrazu:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", "SettingsTabGraphicsAspectRatio16x10": "16:10", "SettingsTabGraphicsAspectRatio21x9": "21:9", "SettingsTabGraphicsAspectRatio32x9": "32:9", "SettingsTabGraphicsAspectRatioStretch": "Rozciągnij do Okna", - "SettingsTabGraphicsDeveloperOptions": "Opcje Programistyczne", - "SettingsTabGraphicsShaderDumpPath": "Ścieżka Zrzutu Shaderów Grafiki:", - "SettingsTabLogging": "Logowanie", - "SettingsTabLoggingLogging": "Logowanie", - "SettingsTabLoggingEnableLoggingToFile": "Włącz Logowanie do Pliku", + "SettingsTabGraphicsDeveloperOptions": "Opcje programisty", + "SettingsTabGraphicsShaderDumpPath": "Ścieżka do zgranych cieni graficznych:", + "SettingsTabLogging": "Dziennik zdarzeń", + "SettingsTabLoggingLogging": "Dziennik zdarzeń", + "SettingsTabLoggingEnableLoggingToFile": "Włącz rejestrowanie zdarzeń do pliku", "SettingsTabLoggingEnableStubLogs": "Wlącz Skróty Logów", "SettingsTabLoggingEnableInfoLogs": "Włącz Logi Informacyjne", "SettingsTabLoggingEnableWarningLogs": "Włącz Logi Ostrzeżeń", @@ -170,18 +175,18 @@ "SettingsTabLoggingEnableTraceLogs": "Włącz Logi Śledzenia", "SettingsTabLoggingEnableGuestLogs": "Włącz Logi Gości", "SettingsTabLoggingEnableFsAccessLogs": "Włącz Logi Dostępu do Systemu Plików", - "SettingsTabLoggingFsGlobalAccessLogMode": "Tryb Globalnych Logów Systemu Plików:", - "SettingsTabLoggingDeveloperOptions": "Opcje programistyczne (OSTRZEŻENIE: Zmniejszą wydajność)", - "SettingsTabLoggingDeveloperOptionsNote": "UWAGA: Zredukuje wydajność", - "SettingsTabLoggingGraphicsBackendLogLevel": "Ilość Logów Backendu Graficznego:", - "SettingsTabLoggingGraphicsBackendLogLevelNone": "Żadne", + "SettingsTabLoggingFsGlobalAccessLogMode": "Tryb globalnego dziennika zdarzeń systemu plików:", + "SettingsTabLoggingDeveloperOptions": "Opcje programisty (UWAGA: wpływa na wydajność)", + "SettingsTabLoggingDeveloperOptionsNote": "UWAGA: Pogrorszy wydajność", + "SettingsTabLoggingGraphicsBackendLogLevel": "Poziom rejestrowania do dziennika zdarzeń Backendu Graficznego:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Nic", "SettingsTabLoggingGraphicsBackendLogLevelError": "Błędy", "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Spowolnienia", - "SettingsTabLoggingGraphicsBackendLogLevelAll": "Wszystkie", - "SettingsTabLoggingEnableDebugLogs": "Włącz Logi Debugowania", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Wszystko", + "SettingsTabLoggingEnableDebugLogs": "Włącz dzienniki zdarzeń do debugowania", "SettingsTabInput": "Sterowanie", - "SettingsTabInputEnableDockedMode": "Tryb Zadokowany", - "SettingsTabInputDirectKeyboardAccess": "Bezpośredni Dostęp do Klawiatury", + "SettingsTabInputEnableDockedMode": "Tryb zadokowany", + "SettingsTabInputDirectKeyboardAccess": "Bezpośredni dostęp do klawiatury", "SettingsButtonSave": "Zapisz", "SettingsButtonClose": "Zamknij", "SettingsButtonOk": "OK", @@ -197,10 +202,10 @@ "ControllerSettingsPlayer7": "Gracz 7", "ControllerSettingsPlayer8": "Gracz 8", "ControllerSettingsHandheld": "Przenośny", - "ControllerSettingsInputDevice": "Urządzenie Wejściowe", + "ControllerSettingsInputDevice": "Urządzenie wejściowe", "ControllerSettingsRefresh": "Odśwież", "ControllerSettingsDeviceDisabled": "Wyłączone", - "ControllerSettingsControllerType": "Typ Kontrolera", + "ControllerSettingsControllerType": "Typ kontrolera", "ControllerSettingsControllerTypeHandheld": "Przenośny", "ControllerSettingsControllerTypeProController": "Pro Kontroler", "ControllerSettingsControllerTypeJoyConPair": "Para JoyCon-ów", @@ -218,7 +223,7 @@ "ControllerSettingsButtonY": "Y", "ControllerSettingsButtonPlus": "+", "ControllerSettingsButtonMinus": "-", - "ControllerSettingsDPad": "Pad Kierunkowy", + "ControllerSettingsDPad": "Krzyżak (D-Pad)", "ControllerSettingsDPadUp": "Góra", "ControllerSettingsDPadDown": "Dół", "ControllerSettingsDPadLeft": "Lewo", @@ -261,79 +266,73 @@ "ControllerSettingsMotionGyroDeadzone": "Deadzone Żyroskopu:", "ControllerSettingsSave": "Zapisz", "ControllerSettingsClose": "Zamknij", - "UserProfilesSelectedUserProfile": "Wybrany Profil Użytkownika:", - "UserProfilesSaveProfileName": "Zapisz Nazwę Profilu", - "UserProfilesChangeProfileImage": "Zmień Obraz Profilu", - "UserProfilesAvailableUserProfiles": "Dostępne Profile Użytkowników:", - "UserProfilesAddNewProfile": "Utwórz Profil", - "UserProfilesDelete": "Usuwać", + "UserProfilesSelectedUserProfile": "Wybrany profil użytkownika:", + "UserProfilesSaveProfileName": "Zapisz nazwę profilu", + "UserProfilesChangeProfileImage": "Zmień obrazek profilu", + "UserProfilesAvailableUserProfiles": "Dostępne profile użytkownika:", + "UserProfilesAddNewProfile": "Utwórz profil", + "UserProfilesDelete": "Usuń", "UserProfilesClose": "Zamknij", "ProfileNameSelectionWatermark": "Wybierz pseudonim", "ProfileImageSelectionTitle": "Wybór Obrazu Profilu", "ProfileImageSelectionHeader": "Wybierz zdjęcie profilowe", "ProfileImageSelectionNote": "Możesz zaimportować niestandardowy obraz profilu lub wybrać awatar z firmware'u systemowego", "ProfileImageSelectionImportImage": "Importuj Plik Obrazu", - "ProfileImageSelectionSelectAvatar": "Wybierz Awatar z Firmware'u", + "ProfileImageSelectionSelectAvatar": "Wybierz domyślny awatar z oprogramowania konsoli", "InputDialogTitle": "Okno Dialogowe Wprowadzania", "InputDialogOk": "OK", "InputDialogCancel": "Anuluj", - "InputDialogAddNewProfileTitle": "Wybierz Nazwę Profilu", - "InputDialogAddNewProfileHeader": "Wprowadź Nazwę Profilu", - "InputDialogAddNewProfileSubtext": "(Maksymalna Długość: {0})", - "AvatarChoose": "Wybierz", - "AvatarSetBackgroundColor": "Ustaw Kolor Tła", + "InputDialogAddNewProfileTitle": "Wybierz nazwę profilu", + "InputDialogAddNewProfileHeader": "Wprowadź nazwę profilu", + "InputDialogAddNewProfileSubtext": "(Maksymalna długość: {0})", + "AvatarChoose": "Wybierz awatar", + "AvatarSetBackgroundColor": "Ustaw kolor tła", "AvatarClose": "Zamknij", - "ControllerSettingsLoadProfileToolTip": "Załaduj Profil", - "ControllerSettingsAddProfileToolTip": "Dodaj Profil", - "ControllerSettingsRemoveProfileToolTip": "Usuń Profil", - "ControllerSettingsSaveProfileToolTip": "Zapisz Profil", - "MenuBarFileToolsTakeScreenshot": "Zrób Zrzut Ekranu", - "MenuBarFileToolsHideUi": "Ukryj UI", + "ControllerSettingsLoadProfileToolTip": "Wczytaj profil", + "ControllerSettingsAddProfileToolTip": "Dodaj profil", + "ControllerSettingsRemoveProfileToolTip": "Usuń profil", + "ControllerSettingsSaveProfileToolTip": "Zapisz profil", + "MenuBarFileToolsTakeScreenshot": "Zrób zrzut ekranu", + "MenuBarFileToolsHideUi": "Ukryj interfejs użytkownika", "GameListContextMenuRunApplication": "Uruchom aplikację ", - "GameListContextMenuToggleFavorite": "Przełącz Ulubione", + "GameListContextMenuToggleFavorite": "Przełącz na ulubione", "GameListContextMenuToggleFavoriteToolTip": "Przełącz status Ulubionej Gry", - "SettingsTabGeneralTheme": "Motyw", - "SettingsTabGeneralThemeCustomTheme": "Ścieżka Niestandardowych Motywów", - "SettingsTabGeneralThemeBaseStyle": "Styl Podstawowy", - "SettingsTabGeneralThemeBaseStyleDark": "Ciemny", - "SettingsTabGeneralThemeBaseStyleLight": "Jasny", - "SettingsTabGeneralThemeEnableCustomTheme": "Włącz Niestandardowy Motyw", - "ButtonBrowse": "Przeglądaj", + "SettingsTabGeneralTheme": "Motyw:", + "SettingsTabGeneralThemeDark": "Ciemny", + "SettingsTabGeneralThemeLight": "Jasny", "ControllerSettingsConfigureGeneral": "Konfiguruj", "ControllerSettingsRumble": "Wibracje", - "ControllerSettingsRumbleStrongMultiplier": "Mocny Mnożnik Wibracji", - "ControllerSettingsRumbleWeakMultiplier": "Słaby Mnożnik Wibracji", - "DialogMessageSaveNotAvailableMessage": "Nie ma danych zapisu dla {0} [{1:x16}]", - "DialogMessageSaveNotAvailableCreateSaveMessage": "Czy chcesz utworzyć dane zapisu dla tej gry?", + "ControllerSettingsRumbleStrongMultiplier": "Mnożnik mocnych wibracji", + "ControllerSettingsRumbleWeakMultiplier": "Mnożnik słabych wibracji", + "DialogMessageSaveNotAvailableMessage": "Nie ma zapisanych danych dla {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Czy chcesz utworzyć zapis danych dla tej gry?", "DialogConfirmationTitle": "Ryujinx - Potwierdzenie", - "DialogUpdaterTitle": "Ryujinx - Aktualizator", + "DialogUpdaterTitle": "Ryujinx - Asystent aktualizacji", "DialogErrorTitle": "Ryujinx - Błąd", - "DialogWarningTitle": "Ryujinx - Uwaga", + "DialogWarningTitle": "Ryujinx - Ostrzeżenie", "DialogExitTitle": "Ryujinx - Wyjdź", "DialogErrorMessage": "Ryujinx napotkał błąd", "DialogExitMessage": "Czy na pewno chcesz zamknąć Ryujinx?", "DialogExitSubMessage": "Wszystkie niezapisane dane zostaną utracone!", - "DialogMessageCreateSaveErrorMessage": "Wystąpił błąd podczas tworzenia określonych danych zapisu: {0}", - "DialogMessageFindSaveErrorMessage": "Wystąpił błąd podczas znajdowania określonych danych zapisu: {0}", - "FolderDialogExtractTitle": "Wybierz folder do rozpakowania", - "DialogNcaExtractionMessage": "Wyodrębnianie sekcji {0} z {1}...", - "DialogNcaExtractionTitle": "Ryujinx - Ekstraktor Sekcji NCA", - "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Niepowodzenie ekstrakcji. W wybranym pliku nie było głównego NCA.", - "DialogNcaExtractionCheckLogErrorMessage": "Niepowodzenie ekstrakcji. Przeczytaj plik dziennika, aby uzyskać więcej informacji.", - "DialogNcaExtractionSuccessMessage": "Ekstrakcja zakończona pomyślnie.", + "DialogMessageCreateSaveErrorMessage": "Wystąpił błąd podczas tworzenia określonych zapisanych danych: {0}", + "DialogMessageFindSaveErrorMessage": "Wystąpił błąd podczas próby znalezienia określonych zapisanych danych: {0}", + "FolderDialogExtractTitle": "Wybierz folder, do którego chcesz rozpakować", + "DialogNcaExtractionMessage": "Wypakowywanie sekcji {0} z {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Asystent wypakowania sekcji NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Niepowodzenie podczas wypakowywania. W wybranym pliku nie było głównego NCA.", + "DialogNcaExtractionCheckLogErrorMessage": "Niepowodzenie podczas wypakowywania. Przeczytaj plik dziennika, aby uzyskać więcej informacji.", + "DialogNcaExtractionSuccessMessage": "Wypakowywanie zakończone pomyślnie.", "DialogUpdaterConvertFailedMessage": "Nie udało się przekonwertować obecnej wersji Ryujinx.", - "DialogUpdaterCancelUpdateMessage": "Anulowanie Aktualizacji!", + "DialogUpdaterCancelUpdateMessage": "Anulowanie aktualizacji!", "DialogUpdaterAlreadyOnLatestVersionMessage": "Używasz już najnowszej wersji Ryujinx!", - "DialogUpdaterFailedToGetVersionMessage": "Wystąpił błąd podczas próby uzyskania informacji o wydaniu z GitHub Release. Może to być spowodowane nową wersją kompilowaną przez GitHub Actions. Spróbuj ponownie za kilka minut.", + "DialogUpdaterFailedToGetVersionMessage": "Wystąpił błąd podczas próby uzyskania informacji o obecnej wersji z GitHub Release. Może to być spowodowane nową wersją kompilowaną przez GitHub Actions. Spróbuj ponownie za kilka minut.", "DialogUpdaterConvertFailedGithubMessage": "Nie udało się przekonwertować otrzymanej wersji Ryujinx z Github Release.", - "DialogUpdaterDownloadingMessage": "Pobieranie Aktualizacji...", + "DialogUpdaterDownloadingMessage": "Pobieranie aktualizacji...", "DialogUpdaterExtractionMessage": "Wypakowywanie Aktualizacji...", "DialogUpdaterRenamingMessage": "Zmiana Nazwy Aktualizacji...", "DialogUpdaterAddingFilesMessage": "Dodawanie Nowej Aktualizacji...", "DialogUpdaterCompleteMessage": "Aktualizacja Zakończona!", "DialogUpdaterRestartMessage": "Czy chcesz teraz zrestartować Ryujinx?", - "DialogUpdaterArchNotSupportedMessage": "Nie używasz obsługiwanej architektury systemu!", - "DialogUpdaterArchNotSupportedSubMessage": "(Obsługiwane są tylko systemy x64!)", "DialogUpdaterNoInternetMessage": "Nie masz połączenia z Internetem!", "DialogUpdaterNoInternetSubMessage": "Sprawdź, czy masz działające połączenie internetowe!", "DialogUpdaterDirtyBuildMessage": "Nie możesz zaktualizować Dirty wersji Ryujinx!", @@ -385,7 +384,10 @@ "DialogUserProfileUnsavedChangesSubMessage": "Czy chcesz odrzucić zmiany?", "DialogControllerSettingsModifiedConfirmMessage": "Aktualne ustawienia kontrolera zostały zaktualizowane.", "DialogControllerSettingsModifiedConfirmSubMessage": "Czy chcesz zapisać?", - "DialogLoadNcaErrorMessage": "{0}. Błędny Plik: {1}", + "DialogLoadFileErrorMessage": "{0}. Błędny plik: {1}", + "DialogModAlreadyExistsMessage": "Modyfikacja już istnieje", + "DialogModInvalidMessage": "Podany katalog nie zawiera modyfikacji!", + "DialogModDeleteNoParentMessage": "Nie udało się usunąć: Nie można odnaleźć katalogu nadrzędnego dla modyfikacji \"{0}\"!", "DialogDlcNoDlcErrorMessage": "Określony plik nie zawiera DLC dla wybranego tytułu!", "DialogPerformanceCheckLoggingEnabledMessage": "Masz włączone rejestrowanie śledzenia, które jest przeznaczone tylko dla programistów.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Aby uzyskać optymalną wydajność, zaleca się wyłączenie rejestrowania śledzenia. Czy chcesz teraz wyłączyć rejestrowanie śledzenia?", @@ -396,6 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "Określony plik nie zawiera aktualizacji dla wybranego tytułu!", "DialogSettingsBackendThreadingWarningTitle": "Ostrzeżenie — Wątki Backend", "DialogSettingsBackendThreadingWarningMessage": "Ryujinx musi zostać ponownie uruchomiony po zmianie tej opcji, aby działał w pełni. W zależności od platformy może być konieczne ręczne wyłączenie sterownika wielowątkowości podczas korzystania z Ryujinx.", + "DialogModManagerDeletionWarningMessage": "Zamierzasz usunąć modyfikacje: {0}\n\nCzy na pewno chcesz kontynuować?", + "DialogModManagerDeletionAllWarningMessage": "Zamierzasz usunąć wszystkie modyfikacje dla wybranego tytułu: {0}\n\nCzy na pewno chcesz kontynuować?", "SettingsTabGraphicsFeaturesOptions": "Funkcje", "SettingsTabGraphicsBackendMultithreading": "Wielowątkowość Backendu Graficznego:", "CommonAuto": "Auto", @@ -430,6 +434,7 @@ "DlcManagerRemoveAllButton": "Usuń Wszystkie", "DlcManagerEnableAllButton": "Włącz Wszystkie", "DlcManagerDisableAllButton": "Wyłącz Wszystkie", + "ModManagerDeleteAllButton": "Usuń wszystko", "MenuBarOptionsChangeLanguage": "Zmień Język", "MenuBarShowFileTypes": "Pokaż typy plików", "CommonSort": "Sortuj", @@ -447,13 +452,13 @@ "CustomThemePathTooltip": "Ścieżka do niestandardowego motywu GUI", "CustomThemeBrowseTooltip": "Wyszukaj niestandardowy motyw GUI", "DockModeToggleTooltip": "Tryb Zadokowany sprawia, że emulowany system zachowuje się jak zadokowany Nintendo Switch. Poprawia to jakość grafiki w większości gier. I odwrotnie, wyłączenie tej opcji sprawi, że emulowany system będzie zachowywał się jak przenośny Nintendo Switch, zmniejszając jakość grafiki.\n\nSkonfiguruj sterowanie gracza 1, jeśli planujesz używać trybu Zadokowanego; Skonfiguruj sterowanie przenośne, jeśli planujesz używać trybu przenośnego.\n\nPozostaw WŁĄCZONY, jeśli nie masz pewności.", - "DirectKeyboardTooltip": "Obsługa bezpośredniego dostępu klawiatury (HID). Zapewnia dostęp gier do klawiatury jako urządzenia do wprowadzania tekstu.", - "DirectMouseTooltip": "Obsługa bezpośredniego dostępu myszy (HID). Zapewnia grom dostęp do myszy jako urządzenia wskazującego.", + "DirectKeyboardTooltip": "Obsługa bezpośredniego dostępu do klawiatury (HID). Zapewnia dostęp gier do klawiatury jako urządzenia do wprowadzania tekstu.\n\nDziała tylko z grami, które natywnie wspierają użycie klawiatury w urządzeniu Switch hardware.\n\nPozostaw wyłączone w razie braku pewności.", + "DirectMouseTooltip": "Obsługa bezpośredniego dostępu do myszy (HID). Zapewnia dostęp gier do myszy jako urządzenia wskazującego.\n\nDziała tylko z grami, które natywnie obsługują przyciski myszy w urządzeniu Switch, które są nieliczne i daleko między nimi.\n\nPo włączeniu funkcja ekranu dotykowego może nie działać.\n\nPozostaw wyłączone w razie wątpliwości.", "RegionTooltip": "Zmień Region Systemu", "LanguageTooltip": "Zmień Język Systemu", "TimezoneTooltip": "Zmień Strefę Czasową Systemu", - "TimeTooltip": "Zmień Czas Systemu", - "VSyncToggleTooltip": "Pionowa synchronizacja emulowanej konsoli. Zasadniczo ogranicznik klatek dla większości gier; wyłączenie jej może spowodować, że gry będą działać z większą szybkością, ekrany wczytywania wydłużą się lub nawet utkną.\n\nMoże być przełączana w grze za pomocą preferowanego skrótu klawiszowego. Zalecamy to zrobić, jeśli planujesz ją wyłączyć.\n\nW razie wątpliwości pozostaw WŁĄCZONĄ", + "TimeTooltip": "Zmień czas systemowy", + "VSyncToggleTooltip": "Synchronizacja pionowa emulowanej konsoli. Zasadniczo ogranicznik klatek dla większości gier; wyłączenie jej może spowodować, że gry będą działać z większą szybkością, ekrany wczytywania wydłużą się lub nawet utkną.\n\nMoże być przełączana w grze za pomocą preferowanego skrótu klawiszowego. Zalecamy to zrobić, jeśli planujesz ją wyłączyć.\n\nW razie wątpliwości pozostaw WŁĄCZONĄ.", "PptcToggleTooltip": "Zapisuje przetłumaczone funkcje JIT, dzięki czemu nie muszą być tłumaczone za każdym razem, gdy gra się ładuje.\n\nZmniejsza zacinanie się i znacznie przyspiesza uruchamianie po pierwszym uruchomieniu gry.\n\nJeśli nie masz pewności, pozostaw WŁĄCZONE", "FsIntegrityToggleTooltip": "Sprawdza pliki podczas uruchamiania gry i jeśli zostaną wykryte uszkodzone pliki, wyświetla w dzienniku błąd hash.\n\nNie ma wpływu na wydajność i ma pomóc w rozwiązywaniu problemów.\n\nPozostaw WŁĄCZONE, jeśli nie masz pewności.", "AudioBackendTooltip": "Zmienia backend używany do renderowania dźwięku.\n\nSDL2 jest preferowany, podczas gdy OpenAL i SoundIO są używane jako rezerwy. Dummy nie będzie odtwarzać dźwięku.\n\nW razie wątpliwości ustaw SDL2.", @@ -467,10 +472,10 @@ "GraphicsBackendThreadingTooltip": "Wykonuje polecenia backend'u graficznego w drugim wątku.\n\nPrzyspiesza kompilację shaderów, zmniejsza zacinanie się i poprawia wydajność sterowników GPU bez własnej obsługi wielowątkowości. Nieco lepsza wydajność w sterownikach z wielowątkowością.\n\nUstaw na AUTO, jeśli nie masz pewności.", "GalThreadingTooltip": "Wykonuje polecenia backend'u graficznego w drugim wątku.\n\nPrzyspiesza kompilację shaderów, zmniejsza zacinanie się i poprawia wydajność sterowników GPU bez własnej obsługi wielowątkowości. Nieco lepsza wydajność w sterownikach z wielowątkowością.\n\nUstaw na AUTO, jeśli nie masz pewności.", "ShaderCacheToggleTooltip": "Zapisuje pamięć podręczną shaderów na dysku, co zmniejsza zacinanie się w kolejnych uruchomieniach.\n\nPozostaw WŁĄCZONE, jeśli nie masz pewności.", - "ResolutionScaleTooltip": "Skala Rozdzielczości zastosowana do odpowiednich celów renderowania", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", "ResolutionScaleEntryTooltip": "Skala rozdzielczości zmiennoprzecinkowej, np. 1,5. Skale niecałkowite częściej powodują problemy lub awarie.", - "AnisotropyTooltip": "Poziom filtrowania anizotropowego (ustaw na Auto, aby użyć wartości wymaganej przez grę)", - "AspectRatioTooltip": "Współczynnik proporcji zastosowany do okna renderowania.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", "ShaderDumpPathTooltip": "Ścieżka Zrzutu Shaderów Grafiki", "FileLogTooltip": "Zapisuje logowanie konsoli w pliku dziennika na dysku. Nie wpływa na wydajność.", "StubLogTooltip": "Wyświetla w konsoli skrótowe komunikaty dziennika. Nie wpływa na wydajność.", @@ -504,6 +509,8 @@ "EnableInternetAccessTooltip": "Pozwala emulowanej aplikacji na łączenie się z Internetem.\n\nGry w trybie LAN mogą łączyć się ze sobą, gdy ta opcja jest włączona, a systemy są połączone z tym samym punktem dostępu. Dotyczy to również prawdziwych konsol.\n\nNie pozwala na łączenie się z serwerami Nintendo. Może powodować awarie niektórych gier, które próbują połączyć się z Internetem.\n\nPozostaw WYŁĄCZONE, jeśli nie masz pewności.", "GameListContextMenuManageCheatToolTip": "Zarządzaj Kodami", "GameListContextMenuManageCheat": "Zarządzaj Kodami", + "GameListContextMenuManageModToolTip": "Zarządzaj modyfikacjami", + "GameListContextMenuManageMod": "Zarządzaj modyfikacjami", "ControllerSettingsStickRange": "Zasięg:", "DialogStopEmulationTitle": "Ryujinx - Zatrzymaj Emulację", "DialogStopEmulationMessage": "Czy na pewno chcesz zatrzymać emulację?", @@ -515,8 +522,6 @@ "SettingsTabCpuMemory": "Pamięć CPU", "DialogUpdaterFlatpakNotSupportedMessage": "Zaktualizuj Ryujinx przez FlatHub.", "UpdaterDisabledWarningTitle": "Aktualizator Wyłączony!", - "GameListContextMenuOpenSdModsDirectory": "Otwórz Katalog Modów Atmosphere", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Otwiera alternatywny katalog Atmosphere na karcie SD, który zawiera modyfikacje aplikacji. Przydatne dla modów, które są pakowane dla prawdziwego sprzętu.", "ControllerSettingsRotate90": "Obróć o 90° w Prawo", "IconSize": "Rozmiar Ikon", "IconSizeTooltip": "Zmień rozmiar ikon gry", @@ -544,12 +549,13 @@ "SwkbdMinCharacters": "Musi mieć co najmniej {0} znaków", "SwkbdMinRangeCharacters": "Musi mieć długość od {0}-{1} znaków", "SoftwareKeyboard": "Klawiatura Oprogramowania", - "SoftwareKeyboardModeNumbersOnly": "Mogą być tylko liczby ", + "SoftwareKeyboardModeNumeric": "Może składać się jedynie z 0-9 lub '.'", "SoftwareKeyboardModeAlphabet": "Nie może zawierać znaków CJK", "SoftwareKeyboardModeASCII": "Musi zawierać tylko tekst ASCII", - "DialogControllerAppletMessagePlayerRange": "Aplikacja żąda {0} graczy z:\n\nTYPY: {1}\n\nGRACZE: {2}\n\n{3}Otwórz Ustawienia i ponownie skonfiguruj Sterowanie lub naciśnij Zamknij.", - "DialogControllerAppletMessage": "Aplikacja żąda dokładnie {0} graczy z:\n\nTYPY: {1}\n\nGRACZE: {2}\n\n{3}Otwórz teraz Ustawienia i ponownie skonfiguruj Sterowanie lub naciśnij Zamknij.", - "DialogControllerAppletDockModeSet": "Ustawiono tryb Zadokowane. Przenośny też jest nieprawidłowy.\n\n", + "ControllerAppletControllers": "Obsługiwane Kontrolery:", + "ControllerAppletPlayers": "Gracze:", + "ControllerAppletDescription": "Twoja aktualna konfiguracja jest nieprawidłowa. Otwórz ustawienia i skonfiguruj swoje wejścia.", + "ControllerAppletDocked": "Ustawiony tryb zadokowany. Sterowanie przenośne powinno być wyłączone.", "UpdaterRenaming": "Zmienianie Nazw Starych Plików...", "UpdaterRenameFailed": "Aktualizator nie mógł zmienić nazwy pliku: {0}", "UpdaterAddingFiles": "Dodawanie Nowych Plików...", @@ -587,6 +593,7 @@ "Writable": "Zapisywalne", "SelectDlcDialogTitle": "Wybierz pliki DLC", "SelectUpdateDialogTitle": "Wybierz pliki aktualizacji", + "SelectModDialogTitle": "Wybierz katalog modów", "UserProfileWindowTitle": "Menedżer Profili Użytkowników", "CheatWindowTitle": "Menedżer Kodów", "DlcWindowTitle": "Menedżer Zawartości do Pobrania", @@ -594,10 +601,12 @@ "CheatWindowHeading": "Kody Dostępne dla {0} [{1}]", "BuildId": "Identyfikator wersji:", "DlcWindowHeading": "{0} Zawartości do Pobrania dostępna dla {1} ({2})", + "ModWindowHeading": "{0} Mod(y/ów)", "UserProfilesEditProfile": "Edytuj Zaznaczone", "Cancel": "Anuluj", "Save": "Zapisz", "Discard": "Odrzuć", + "Paused": "Wstrzymano", "UserProfilesSetProfileImage": "Ustaw Obraz Profilu", "UserProfileEmptyNameError": "Nazwa jest wymagana", "UserProfileNoImageError": "Należy ustawić obraz profilowy", @@ -607,9 +616,9 @@ "UserProfilesName": "Nazwa:", "UserProfilesUserId": "ID Użytkownika:", "SettingsTabGraphicsBackend": "Backend Graficzny", - "SettingsTabGraphicsBackendTooltip": "Używalne Backendy Graficzne", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", "SettingsEnableTextureRecompression": "Włącz Rekompresję Tekstur", - "SettingsEnableTextureRecompressionTooltip": "Kompresuje niektóre tekstury w celu zmniejszenia zużycia pamięci VRAM.\n\nZalecane do użytku z GPU, które mają mniej niż 4 GiB pamięci VRAM.\n\nW razie wątpliwości pozostaw WYŁĄCZONE.", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", "SettingsTabGraphicsPreferredGpu": "Preferowane GPU", "SettingsTabGraphicsPreferredGpuTooltip": "Wybierz kartę graficzną, która będzie używana z backendem graficznym Vulkan.\n\nNie wpływa na GPU używane przez OpenGL.\n\nW razie wątpliwości ustaw flagę GPU jako \"dGPU\". Jeśli żadnej nie ma, pozostaw nietknięte.", "SettingsAppRequiredRestartMessage": "Wymagane Zrestartowanie Ryujinx", @@ -620,8 +629,8 @@ "SettingsTabHotkeysVolumeDownHotkey": "Zmniejsz Głośność:", "SettingsEnableMacroHLE": "Włącz Macro HLE", "SettingsEnableMacroHLETooltip": "Wysokopoziomowa emulacja kodu GPU Macro.\n\nPoprawia wydajność, ale może powodować błędy graficzne w niektórych grach.\n\nW razie wątpliwości pozostaw WŁĄCZONE.", - "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", - "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", + "SettingsEnableColorSpacePassthrough": "Przekazywanie przestrzeni kolorów", + "SettingsEnableColorSpacePassthroughTooltip": "Nakazuje API Vulkan przekazywać informacje o kolorze bez określania przestrzeni kolorów. Dla użytkowników z wyświetlaczami o szerokim zakresie kolorów może to skutkować bardziej żywymi kolorami, kosztem ich poprawności.", "VolumeShort": "Głoś", "UserProfilesManageSaves": "Zarządzaj Zapisami", "DeleteUserSave": "Czy chcesz usunąć zapis użytkownika dla tej gry?", @@ -635,12 +644,12 @@ "Recover": "Odzyskaj", "UserProfilesRecoverHeading": "Znaleziono zapisy dla następujących kont", "UserProfilesRecoverEmptyList": "Brak profili do odzyskania", - "GraphicsAATooltip": "Stosuje antyaliasing do renderowania gry", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", "GraphicsAALabel": "Antyaliasing:", "GraphicsScalingFilterLabel": "Filtr skalowania:", - "GraphicsScalingFilterTooltip": "Włącza skalowanie bufora ramki", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", "GraphicsScalingFilterLevelLabel": "Poziom", - "GraphicsScalingFilterLevelTooltip": "Ustaw poziom filtra skalowania", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "SMAA Niskie", "SmaaMedium": "SMAA Średnie", "SmaaHigh": "SMAA Wysokie", @@ -648,9 +657,12 @@ "UserEditorTitle": "Edytuj użytkownika", "UserEditorTitleCreate": "Utwórz użytkownika", "SettingsTabNetworkInterface": "Interfejs sieci:", - "NetworkInterfaceTooltip": "Interfejs sieciowy używany do funkcji LAN", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", "NetworkInterfaceDefault": "Domyślny", "PackagingShaders": "Pakuje Shadery ", "AboutChangelogButton": "Zobacz listę zmian na GitHubie", - "AboutChangelogButtonTooltipMessage": "Kliknij, aby otworzyć listę zmian dla tej wersji w domyślnej przeglądarce." -} \ No newline at end of file + "AboutChangelogButtonTooltipMessage": "Kliknij, aby otworzyć listę zmian dla tej wersji w domyślnej przeglądarce.", + "SettingsTabNetworkMultiplayer": "Gra Wieloosobowa", + "MultiplayerMode": "Tryb:", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." +} diff --git a/src/Ryujinx/Assets/Locales/pt_BR.json b/src/Ryujinx/Assets/Locales/pt_BR.json index 8909a84fec..cd6a2fd1c8 100644 --- a/src/Ryujinx/Assets/Locales/pt_BR.json +++ b/src/Ryujinx/Assets/Locales/pt_BR.json @@ -30,7 +30,7 @@ "MenuBarToolsManageFileTypes": "Gerenciar tipos de arquivo", "MenuBarToolsInstallFileTypes": "Instalar tipos de arquivo", "MenuBarToolsUninstallFileTypes": "Desinstalar tipos de arquivos", - "MenuBarHelp": "A_juda", + "MenuBarHelp": "_Ajuda", "MenuBarHelpCheckForUpdates": "_Verificar se há atualizações", "MenuBarHelpAbout": "_Sobre", "MenuSearch": "Buscar...", @@ -54,8 +54,6 @@ "GameListContextMenuManageTitleUpdatesToolTip": "Abre a janela de gerenciamento de atualizações", "GameListContextMenuManageDlc": "Gerenciar DLCs", "GameListContextMenuManageDlcToolTip": "Abre a janela de gerenciamento de DLCs", - "GameListContextMenuOpenModsDirectory": "Abrir diretório de mods", - "GameListContextMenuOpenModsDirectoryToolTip": "Abre o diretório que contém modificações (mods) do jogo", "GameListContextMenuCacheManagement": "Gerenciamento de cache", "GameListContextMenuCacheManagementPurgePptc": "Limpar cache PPTC", "GameListContextMenuCacheManagementPurgePptcToolTip": "Deleta o cache PPTC armazenado em disco do jogo", @@ -72,15 +70,22 @@ "GameListContextMenuExtractDataRomFSToolTip": "Extrai a seção RomFS do jogo (incluindo atualizações)", "GameListContextMenuExtractDataLogo": "Logo", "GameListContextMenuExtractDataLogoToolTip": "Extrai a seção Logo do jogo (incluindo atualizações)", + "GameListContextMenuCreateShortcut": "Criar atalho da aplicação", + "GameListContextMenuCreateShortcutToolTip": "Criar um atalho de área de trabalho que inicia o aplicativo selecionado", + "GameListContextMenuCreateShortcutToolTipMacOS": "Crie um atalho na pasta Aplicativos do macOS que abre o Aplicativo selecionado", + "GameListContextMenuOpenModsDirectory": "Abrir pasta de Mods", + "GameListContextMenuOpenModsDirectoryToolTip": "Abre a pasta que contém os mods da aplicação ", + "GameListContextMenuOpenSdModsDirectory": "Abrir diretório de mods Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", "StatusBarGamesLoaded": "{0}/{1} jogos carregados", "StatusBarSystemVersion": "Versão do firmware: {0}", - "LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected", - "LinuxVmMaxMapCountDialogTextPrimary": "Would you like to increase the value of vm.max_map_count to {0}", - "LinuxVmMaxMapCountDialogTextSecondary": "Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.", - "LinuxVmMaxMapCountDialogButtonUntilRestart": "Yes, until the next restart", - "LinuxVmMaxMapCountDialogButtonPersistent": "Yes, permanently", - "LinuxVmMaxMapCountWarningTextPrimary": "Max amount of memory mappings is lower than recommended.", - "LinuxVmMaxMapCountWarningTextSecondary": "The current value of vm.max_map_count ({0}) is lower than {1}. Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.\n\nYou might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that.", + "LinuxVmMaxMapCountDialogTitle": "Limite baixo para mapeamentos de memória detectado", + "LinuxVmMaxMapCountDialogTextPrimary": "Você gostaria de aumentar o valor de vm.max_map_count para {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "Alguns jogos podem tentar criar mais mapeamentos de memória do que o atualmente permitido. Ryujinx irá falhar assim que este limite for excedido.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Sim, até a próxima reinicialização", + "LinuxVmMaxMapCountDialogButtonPersistent": "Sim, permanentemente", + "LinuxVmMaxMapCountWarningTextPrimary": "A quantidade máxima de mapeamentos de memória é menor que a recomendada.", + "LinuxVmMaxMapCountWarningTextSecondary": "O valor atual de vm.max_map_count ({0}) é menor que {1}. Alguns jogos podem tentar criar mais mapeamentos de memória do que o permitido no momento. Ryujinx vai falhar assim que este limite for excedido.\n\nTalvez você queira aumentar o limite manualmente ou instalar pkexec, o que permite que Ryujinx ajude com isso.", "Settings": "Configurações", "SettingsTabGeneral": "Geral", "SettingsTabGeneralGeneral": "Geral", @@ -150,7 +155,7 @@ "SettingsTabGraphicsResolutionScaleNative": "Nativa (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (não recomendado)", "SettingsTabGraphicsAspectRatio": "Proporção:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -223,15 +228,15 @@ "ControllerSettingsDPadDown": "Baixo", "ControllerSettingsDPadLeft": "Esquerda", "ControllerSettingsDPadRight": "Direita", - "ControllerSettingsStickButton": "Button", - "ControllerSettingsStickUp": "Up", - "ControllerSettingsStickDown": "Down", - "ControllerSettingsStickLeft": "Left", - "ControllerSettingsStickRight": "Right", - "ControllerSettingsStickStick": "Stick", - "ControllerSettingsStickInvertXAxis": "Invert Stick X", - "ControllerSettingsStickInvertYAxis": "Invert Stick Y", - "ControllerSettingsStickDeadzone": "Deadzone:", + "ControllerSettingsStickButton": "Botão", + "ControllerSettingsStickUp": "Cima", + "ControllerSettingsStickDown": "Baixo", + "ControllerSettingsStickLeft": "Esquerda", + "ControllerSettingsStickRight": "Direita", + "ControllerSettingsStickStick": "Analógico", + "ControllerSettingsStickInvertXAxis": "Inverter eixo X", + "ControllerSettingsStickInvertYAxis": "Inverter eixo Y", + "ControllerSettingsStickDeadzone": "Zona morta:", "ControllerSettingsLStick": "Analógico esquerdo", "ControllerSettingsRStick": "Analógico direito", "ControllerSettingsTriggersLeft": "Gatilhos esquerda", @@ -289,16 +294,12 @@ "ControllerSettingsSaveProfileToolTip": "Salvar perfil", "MenuBarFileToolsTakeScreenshot": "Salvar captura de tela", "MenuBarFileToolsHideUi": "Esconder Interface", - "GameListContextMenuRunApplication": "Run Application", + "GameListContextMenuRunApplication": "Executar Aplicativo", "GameListContextMenuToggleFavorite": "Alternar favorito", "GameListContextMenuToggleFavoriteToolTip": "Marca ou desmarca jogo como favorito", - "SettingsTabGeneralTheme": "Tema", - "SettingsTabGeneralThemeCustomTheme": "Diretório de tema customizado", - "SettingsTabGeneralThemeBaseStyle": "Estilo base", - "SettingsTabGeneralThemeBaseStyleDark": "Escuro", - "SettingsTabGeneralThemeBaseStyleLight": "Claro", - "SettingsTabGeneralThemeEnableCustomTheme": "Habilitar tema customizado", - "ButtonBrowse": "Procurar", + "SettingsTabGeneralTheme": "Tema:", + "SettingsTabGeneralThemeDark": "Escuro", + "SettingsTabGeneralThemeLight": "Claro", "ControllerSettingsConfigureGeneral": "Configurar", "ControllerSettingsRumble": "Vibração", "ControllerSettingsRumbleStrongMultiplier": "Multiplicador de vibração forte", @@ -332,8 +333,6 @@ "DialogUpdaterAddingFilesMessage": "Adicionando nova atualização...", "DialogUpdaterCompleteMessage": "Atualização concluída!", "DialogUpdaterRestartMessage": "Deseja reiniciar o Ryujinx agora?", - "DialogUpdaterArchNotSupportedMessage": "Você não está rodando uma arquitetura de sistema suportada!", - "DialogUpdaterArchNotSupportedSubMessage": "(Apenas sistemas x64 são suportados!)", "DialogUpdaterNoInternetMessage": "Você não está conectado à Internet!", "DialogUpdaterNoInternetSubMessage": "Por favor, certifique-se de que você tem uma conexão funcional à Internet!", "DialogUpdaterDirtyBuildMessage": "Você não pode atualizar uma compilação Dirty do Ryujinx!", @@ -385,7 +384,10 @@ "DialogUserProfileUnsavedChangesSubMessage": "Deseja descartar as alterações?", "DialogControllerSettingsModifiedConfirmMessage": "As configurações de controle atuais foram atualizadas.", "DialogControllerSettingsModifiedConfirmSubMessage": "Deseja salvar?", - "DialogLoadNcaErrorMessage": "{0}. Arquivo com erro: {1}", + "DialogLoadFileErrorMessage": "{0}. Errored File: {1}", + "DialogModAlreadyExistsMessage": "Mod already exists", + "DialogModInvalidMessage": "The specified directory does not contain a mod!", + "DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!", "DialogDlcNoDlcErrorMessage": "O arquivo especificado não contém DLCs para o título selecionado!", "DialogPerformanceCheckLoggingEnabledMessage": "Os logs de depuração estão ativos, esse recurso é feito para ser usado apenas por desenvolvedores.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Para melhor performance, é recomendável desabilitar os logs de depuração. Gostaria de desabilitar os logs de depuração agora?", @@ -396,6 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "O arquivo especificado não contém atualizações para o título selecionado!", "DialogSettingsBackendThreadingWarningTitle": "Alerta - Threading da API gráfica", "DialogSettingsBackendThreadingWarningMessage": "Ryujinx precisa ser reiniciado após mudar essa opção para que ela tenha efeito. Dependendo da sua plataforma, pode ser preciso desabilitar o multithreading do driver de vídeo quando usar o Ryujinx.", + "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", "SettingsTabGraphicsFeaturesOptions": "Recursos", "SettingsTabGraphicsBackendMultithreading": "Multithreading da API gráfica:", "CommonAuto": "Automático", @@ -430,6 +434,7 @@ "DlcManagerRemoveAllButton": "Remover todos", "DlcManagerEnableAllButton": "Habilitar todos", "DlcManagerDisableAllButton": "Desabilitar todos", + "ModManagerDeleteAllButton": "Delete All", "MenuBarOptionsChangeLanguage": "Mudar idioma", "MenuBarShowFileTypes": "Mostrar tipos de arquivo", "CommonSort": "Ordenar", @@ -447,13 +452,13 @@ "CustomThemePathTooltip": "Diretório do tema customizado", "CustomThemeBrowseTooltip": "Navegar até um tema customizado", "DockModeToggleTooltip": "Habilita ou desabilita modo TV", - "DirectKeyboardTooltip": "Habilita ou desabilita \"acesso direto ao teclado (HID)\" (Permite que o jogo acesse o seu teclado como dispositivo de entrada de texto)", - "DirectMouseTooltip": "Habilita ou desabilita \"acesso direto ao mouse (HID)\" (Permite que o jogo acesse o seu mouse como dispositivo apontador)", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", "RegionTooltip": "Mudar a região do sistema", "LanguageTooltip": "Mudar o idioma do sistema", "TimezoneTooltip": "Mudar o fuso-horário do sistema", "TimeTooltip": "Mudar a hora do sistema", - "VSyncToggleTooltip": "Habilita ou desabilita a sincronia vertical", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", "PptcToggleTooltip": "Habilita ou desabilita PPTC", "FsIntegrityToggleTooltip": "Habilita ou desabilita verificação de integridade dos arquivos do jogo", "AudioBackendTooltip": "Mudar biblioteca de áudio", @@ -467,10 +472,10 @@ "GraphicsBackendThreadingTooltip": "Habilita multithreading do backend gráfico", "GalThreadingTooltip": "Executa comandos do backend gráfico em uma segunda thread. Permite multithreading em tempo de execução da compilação de shader, diminui os travamentos, e melhora performance em drivers sem suporte embutido a multithreading. Pequena variação na performance máxima em drivers com suporte a multithreading. Ryujinx pode precisar ser reiniciado para desabilitar adequadamente o multithreading embutido do driver, ou você pode precisar fazer isso manualmente para ter a melhor performance.", "ShaderCacheToggleTooltip": "Habilita ou desabilita o cache de shader", - "ResolutionScaleTooltip": "Escala de resolução aplicada às texturas de renderização", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", "ResolutionScaleEntryTooltip": "Escala de resolução de ponto flutuante, como 1.5. Valores não inteiros tem probabilidade maior de causar problemas ou quebras.", - "AnisotropyTooltip": "Nível de filtragem anisotrópica (deixe em Auto para usar o valor solicitado pelo jogo)", - "AspectRatioTooltip": "Taxa de proporção aplicada à janela do renderizador.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", "ShaderDumpPathTooltip": "Diretòrio de despejo de shaders", "FileLogTooltip": "Habilita ou desabilita log para um arquivo no disco", "StubLogTooltip": "Habilita ou desabilita exibição de mensagens de stub", @@ -504,6 +509,8 @@ "EnableInternetAccessTooltip": "Habilita acesso à internet do programa convidado. Se habilitado, o aplicativo vai se comportar como se o sistema Switch emulado estivesse conectado a Internet. Note que em alguns casos, aplicativos podem acessar a Internet mesmo com essa opção desabilitada", "GameListContextMenuManageCheatToolTip": "Gerenciar Cheats", "GameListContextMenuManageCheat": "Gerenciar Cheats", + "GameListContextMenuManageModToolTip": "Manage Mods", + "GameListContextMenuManageMod": "Manage Mods", "ControllerSettingsStickRange": "Intervalo:", "DialogStopEmulationTitle": "Ryujinx - Parar emulação", "DialogStopEmulationMessage": "Tem certeza que deseja parar a emulação?", @@ -515,8 +522,6 @@ "SettingsTabCpuMemory": "Memória da CPU", "DialogUpdaterFlatpakNotSupportedMessage": "Por favor, atualize o Ryujinx pelo FlatHub.", "UpdaterDisabledWarningTitle": "Atualizador desabilitado!", - "GameListContextMenuOpenSdModsDirectory": "Abrir diretório de mods Atmosphere", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Abre o diretório alternativo Atmosphere no cartão SD que contém mods para o aplicativo", "ControllerSettingsRotate90": "Rodar 90° sentido horário", "IconSize": "Tamanho do ícone", "IconSizeTooltip": "Muda o tamanho do ícone do jogo", @@ -544,12 +549,13 @@ "SwkbdMinCharacters": "Deve ter pelo menos {0} caracteres", "SwkbdMinRangeCharacters": "Deve ter entre {0}-{1} caracteres", "SoftwareKeyboard": "Teclado por Software", - "SoftwareKeyboardModeNumbersOnly": "Must be numbers only", - "SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only", - "SoftwareKeyboardModeASCII": "Must be ASCII text only", - "DialogControllerAppletMessagePlayerRange": "O aplicativo requer {0} jogador(es) com:\n\nTIPOS: {1}\n\nJOGADORES: {2}\n\n{3}Por favor, abra as configurações e reconfigure os controles agora ou clique em Fechar.", - "DialogControllerAppletMessage": "O aplicativo requer exatamente {0} jogador(es) com:\n\nTIPOS: {1}\n\nJOGADORES: {2}\n\n{3}Por favor, abra as configurações e reconfigure os controles agora ou clique em Fechar.", - "DialogControllerAppletDockModeSet": "Modo TV ativado. Portátil também não é válido.\n\n", + "SoftwareKeyboardModeNumeric": "Deve ser somente 0-9 ou '.'", + "SoftwareKeyboardModeAlphabet": "Apenas devem ser caracteres não CJK.", + "SoftwareKeyboardModeASCII": "Deve ser apenas texto ASCII", + "ControllerAppletControllers": "Supported Controllers:", + "ControllerAppletPlayers": "Players:", + "ControllerAppletDescription": "Your current configuration is invalid. Open settings and reconfigure your inputs.", + "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.", "UpdaterRenaming": "Renomeando arquivos antigos...", "UpdaterRenameFailed": "O atualizador não conseguiu renomear o arquivo: {0}", "UpdaterAddingFiles": "Adicionando novos arquivos...", @@ -587,17 +593,20 @@ "Writable": "Gravável", "SelectDlcDialogTitle": "Selecionar arquivos de DLC", "SelectUpdateDialogTitle": "Selecionar arquivos de atualização", + "SelectModDialogTitle": "Select mod directory", "UserProfileWindowTitle": "Gerenciador de perfis de usuário", "CheatWindowTitle": "Gerenciador de Cheats", "DlcWindowTitle": "Gerenciador de DLC", "UpdateWindowTitle": "Gerenciador de atualizações", "CheatWindowHeading": "Cheats disponíveis para {0} [{1}]", - "BuildId": "BuildId:", + "BuildId": "ID da Build", "DlcWindowHeading": "{0} DLCs disponíveis para {1} ({2})", + "ModWindowHeading": "{0} Mod(s)", "UserProfilesEditProfile": "Editar selecionado", "Cancel": "Cancelar", "Save": "Salvar", "Discard": "Descartar", + "Paused": "Paused", "UserProfilesSetProfileImage": "Definir imagem de perfil", "UserProfileEmptyNameError": "É necessário um nome", "UserProfileNoImageError": "A imagem de perfil deve ser definida", @@ -607,9 +616,9 @@ "UserProfilesName": "Nome:", "UserProfilesUserId": "ID de usuário:", "SettingsTabGraphicsBackend": "Backend gráfico", - "SettingsTabGraphicsBackendTooltip": "Backend gráfico a ser usado", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", "SettingsEnableTextureRecompression": "Habilitar recompressão de texturas", - "SettingsEnableTextureRecompressionTooltip": "Comprime certas texturas para reduzir o uso da VRAM.\n\nRecomendado para uso com GPUs com menos de 4GB VRAM.\n\nEm caso de dúvida, deixe DESLIGADO.", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", "SettingsTabGraphicsPreferredGpu": "GPU preferencial", "SettingsTabGraphicsPreferredGpuTooltip": "Selecione a placa de vídeo que será usada com o backend gráfico Vulkan.\n\nNão afeta a GPU que OpenGL usará.\n\nSelecione \"dGPU\" em caso de dúvida. Se não houver nenhuma, não mexa.", "SettingsAppRequiredRestartMessage": "Reinicialização do Ryujinx necessária", @@ -620,8 +629,8 @@ "SettingsTabHotkeysVolumeDownHotkey": "Diminuir volume:", "SettingsEnableMacroHLE": "Habilitar emulação de alto nível para Macros", "SettingsEnableMacroHLETooltip": "Habilita emulação de alto nível de códigos Macro da GPU.\n\nMelhora a performance, mas pode causar problemas gráficos em alguns jogos.\n\nEm caso de dúvida, deixe ATIVADO.", - "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", - "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", + "SettingsEnableColorSpacePassthrough": "Passagem de Espaço Cor", + "SettingsEnableColorSpacePassthroughTooltip": "Direciona o backend Vulkan para passar informações de cores sem especificar um espaço de cores. Para usuários com telas de ampla gama, isso pode resultar em cores mais vibrantes, ao custo da correção de cores.", "VolumeShort": "Vol", "UserProfilesManageSaves": "Gerenciar jogos salvos", "DeleteUserSave": "Deseja apagar o jogo salvo do usuário para este jogo?", @@ -635,12 +644,12 @@ "Recover": "Recuperar", "UserProfilesRecoverHeading": "Jogos salvos foram encontrados para as seguintes contas", "UserProfilesRecoverEmptyList": "Nenhum perfil para recuperar", - "GraphicsAATooltip": "Aplica anti-serrilhamento à renderização do jogo", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", "GraphicsAALabel": "Anti-serrilhado:", "GraphicsScalingFilterLabel": "Filtro de escala:", - "GraphicsScalingFilterTooltip": "Habilita escala do Framebuffer", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", "GraphicsScalingFilterLevelLabel": "Nível", - "GraphicsScalingFilterLevelTooltip": "Define o nível do filtro de escala", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "SMAA Baixo", "SmaaMedium": "SMAA Médio", "SmaaHigh": "SMAA Alto", @@ -648,9 +657,12 @@ "UserEditorTitle": "Editar usuário", "UserEditorTitleCreate": "Criar usuário", "SettingsTabNetworkInterface": "Interface de rede:", - "NetworkInterfaceTooltip": "A interface de rede usada para recursos LAN (rede local)", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", "NetworkInterfaceDefault": "Padrão", - "PackagingShaders": "Packaging Shaders", - "AboutChangelogButton": "View Changelog on GitHub", - "AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser." -} \ No newline at end of file + "PackagingShaders": "Empacotamento de Shaders", + "AboutChangelogButton": "Ver mudanças no GitHub", + "AboutChangelogButtonTooltipMessage": "Clique para abrir o relatório de alterações para esta versão no seu navegador padrão.", + "SettingsTabNetworkMultiplayer": "Multiplayer", + "MultiplayerMode": "Modo:", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." +} diff --git a/src/Ryujinx/Assets/Locales/ru_RU.json b/src/Ryujinx/Assets/Locales/ru_RU.json index a2128e52e5..e7c36f6ca4 100644 --- a/src/Ryujinx/Assets/Locales/ru_RU.json +++ b/src/Ryujinx/Assets/Locales/ru_RU.json @@ -1,37 +1,37 @@ { - "Language": "Русский", + "Language": "Русский (RU)", "MenuBarFileOpenApplet": "Открыть апплет", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Открыть апплет Mii Editor в автономном режиме", - "SettingsTabInputDirectMouseAccess": "Прямой доступ к мыши", + "SettingsTabInputDirectMouseAccess": "Прямой ввод мыши", "SettingsTabSystemMemoryManagerMode": "Режим диспетчера памяти:", "SettingsTabSystemMemoryManagerModeSoftware": "Программное обеспечение", "SettingsTabSystemMemoryManagerModeHost": "Хост (быстро)", "SettingsTabSystemMemoryManagerModeHostUnchecked": "Хост не установлен (самый быстрый, небезопасный)", - "SettingsTabSystemUseHypervisor": "Использовать Гипервизор", + "SettingsTabSystemUseHypervisor": "Использовать Hypervisor", "MenuBarFile": "_Файл", - "MenuBarFileOpenFromFile": "_Загрузить приложение из файла", - "MenuBarFileOpenUnpacked": "Загрузить _Распакованную игру", + "MenuBarFileOpenFromFile": "_Добавить приложение из файла", + "MenuBarFileOpenUnpacked": "Добавить _распакованную игру", "MenuBarFileOpenEmuFolder": "Открыть папку Ryujinx", - "MenuBarFileOpenLogsFolder": "Открыть папку журналов", + "MenuBarFileOpenLogsFolder": "Открыть папку с логами", "MenuBarFileExit": "_Выход", - "MenuBarOptions": "Опции", + "MenuBarOptions": "_Настройки", "MenuBarOptionsToggleFullscreen": "Включить полноэкранный режим", - "MenuBarOptionsStartGamesInFullscreen": "Запустить игру в полноэкранном режиме", + "MenuBarOptionsStartGamesInFullscreen": "Запускать игры в полноэкранном режиме", "MenuBarOptionsStopEmulation": "Остановить эмуляцию", "MenuBarOptionsSettings": "_Параметры", - "MenuBarOptionsManageUserProfiles": "_Управление профилями пользователей", + "MenuBarOptionsManageUserProfiles": "_Менеджер учетных записей", "MenuBarActions": "_Действия", "MenuBarOptionsSimulateWakeUpMessage": "Имитировать сообщение пробуждения", "MenuBarActionsScanAmiibo": "Сканировать Amiibo", "MenuBarTools": "_Инструменты", - "MenuBarToolsInstallFirmware": "Установить прошивку", + "MenuBarToolsInstallFirmware": "Установка прошивки", "MenuBarFileToolsInstallFirmwareFromFile": "Установить прошивку из XCI или ZIP", "MenuBarFileToolsInstallFirmwareFromDirectory": "Установить прошивку из папки", "MenuBarToolsManageFileTypes": "Управление типами файлов", "MenuBarToolsInstallFileTypes": "Установить типы файлов", "MenuBarToolsUninstallFileTypes": "Удалить типы файлов", - "MenuBarHelp": "Помощь", - "MenuBarHelpCheckForUpdates": "Проверка обновления", + "MenuBarHelp": "_Помощь", + "MenuBarHelpCheckForUpdates": "Проверка обновлений", "MenuBarHelpAbout": "О программе", "MenuSearch": "Поиск...", "GameListHeaderFavorite": "Избранные", @@ -39,23 +39,21 @@ "GameListHeaderApplication": "Название", "GameListHeaderDeveloper": "Разработчик", "GameListHeaderVersion": "Версия", - "GameListHeaderTimePlayed": "Время воспроизведения", + "GameListHeaderTimePlayed": "Время в игре", "GameListHeaderLastPlayed": "Последняя игра", "GameListHeaderFileExtension": "Расширение файла", "GameListHeaderFileSize": "Размер файла", "GameListHeaderPath": "Путь", - "GameListContextMenuOpenUserSaveDirectory": "Открыть папку сохранений пользователя", + "GameListContextMenuOpenUserSaveDirectory": "Открыть папку с сохранениями", "GameListContextMenuOpenUserSaveDirectoryToolTip": "Открывает папку, содержащую пользовательские сохранения", "GameListContextMenuOpenDeviceSaveDirectory": "Открыть папку сохраненных устройств", "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Открывает папку, содержащую сохраненные устройства", "GameListContextMenuOpenBcatSaveDirectory": "Открыть папку сохраненных BCAT", "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Открывает папку, содержащую сохраненные BCAT.", - "GameListContextMenuManageTitleUpdates": "Управление обновлениями названий", + "GameListContextMenuManageTitleUpdates": "Управление обновлениями", "GameListContextMenuManageTitleUpdatesToolTip": "Открывает окно управления обновлением заголовков", "GameListContextMenuManageDlc": "Управление DLC", "GameListContextMenuManageDlcToolTip": "Открывает окно управления DLC", - "GameListContextMenuOpenModsDirectory": "Открыть папку с модами", - "GameListContextMenuOpenModsDirectoryToolTip": "Открывает папку, содержащую моды приложений и игр", "GameListContextMenuCacheManagement": "Управление кэшем", "GameListContextMenuCacheManagementPurgePptc": "Перестройка очереди PPTC", "GameListContextMenuCacheManagementPurgePptcToolTip": "Запускает перестройку PPTC во время запуска следующей игры.", @@ -72,8 +70,15 @@ "GameListContextMenuExtractDataRomFSToolTip": "Извлечение раздела RomFS из текущих настроек приложения (включая обновления)", "GameListContextMenuExtractDataLogo": "Логотип", "GameListContextMenuExtractDataLogoToolTip": "Извлечение раздела с логотипом из текущих настроек приложения (включая обновления)", + "GameListContextMenuCreateShortcut": "Создать ярлык приложения", + "GameListContextMenuCreateShortcutToolTip": "Создать ярлык на рабочем столе, с помощью которого можно запустить игру или приложение", + "GameListContextMenuCreateShortcutToolTipMacOS": "Создать ярлык игры или приложения в папке Программы macOS", + "GameListContextMenuOpenModsDirectory": "Открыть папку с модами", + "GameListContextMenuOpenModsDirectoryToolTip": "Открывает папку, содержащую моды для приложений и игр", + "GameListContextMenuOpenSdModsDirectory": "Открыть папку с модами Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Открывает альтернативную папку Atmosphere SD-карты, которая содержит моды для приложений и игр. Полезно для модов, сделанных для реальной консоли.", "StatusBarGamesLoaded": "{0}/{1} Игр загружено", - "StatusBarSystemVersion": "Версия системы: {0}", + "StatusBarSystemVersion": "Версия прошивки: {0}", "LinuxVmMaxMapCountDialogTitle": "Обнаружен низкий лимит разметки памяти", "LinuxVmMaxMapCountDialogTextPrimary": "Вы хотите увеличить значение vm.max_map_count до {0}", "LinuxVmMaxMapCountDialogTextSecondary": "Некоторые игры могут создавать большую разметку памяти, чем разрешено на данный момент по умолчанию. Ryujinx вылетит при превышении этого лимита.", @@ -82,21 +87,21 @@ "LinuxVmMaxMapCountWarningTextPrimary": "Максимальная разметка памяти меньше, чем рекомендуется.", "LinuxVmMaxMapCountWarningTextSecondary": "Текущее значение vm.max_map_count ({0}) меньше, чем {1}. Некоторые игры могут попытаться создать большую разметку памяти, чем разрешено в данный момент. Ryujinx вылетит как только этот лимит будет превышен.\n\nВозможно, вы захотите вручную увеличить лимит или установить pkexec, что позволит Ryujinx помочь справиться с превышением лимита.", "Settings": "Параметры", - "SettingsTabGeneral": "Пользовательский интерфейс", + "SettingsTabGeneral": "Интерфейс", "SettingsTabGeneralGeneral": "Общее", - "SettingsTabGeneralEnableDiscordRichPresence": "Включить Discord Rich Presence", + "SettingsTabGeneralEnableDiscordRichPresence": "Включить статус активности в Discord", "SettingsTabGeneralCheckUpdatesOnLaunch": "Проверять наличие обновлений при запуске", - "SettingsTabGeneralShowConfirmExitDialog": "Показать диалоговое окно \"Подтвердить выход\"", - "SettingsTabGeneralHideCursor": "Скрыть курсор", + "SettingsTabGeneralShowConfirmExitDialog": "Подтверждать выход из приложения", + "SettingsTabGeneralHideCursor": "Скрывать курсор", "SettingsTabGeneralHideCursorNever": "Никогда", - "SettingsTabGeneralHideCursorOnIdle": "Скрыть курсор в режиме ожидания", + "SettingsTabGeneralHideCursorOnIdle": "В режиме ожидания", "SettingsTabGeneralHideCursorAlways": "Всегда", "SettingsTabGeneralGameDirectories": "Папки с играми", "SettingsTabGeneralAdd": "Добавить", "SettingsTabGeneralRemove": "Удалить", "SettingsTabSystem": "Система", "SettingsTabSystemCore": "Основные настройки", - "SettingsTabSystemSystemRegion": "Регион Системы:", + "SettingsTabSystemSystemRegion": "Регион прошивки:", "SettingsTabSystemSystemRegionJapan": "Япония", "SettingsTabSystemSystemRegionUSA": "США", "SettingsTabSystemSystemRegionEurope": "Европа", @@ -104,7 +109,7 @@ "SettingsTabSystemSystemRegionChina": "Китай", "SettingsTabSystemSystemRegionKorea": "Корея", "SettingsTabSystemSystemRegionTaiwan": "Тайвань", - "SettingsTabSystemSystemLanguage": "Язык системы:", + "SettingsTabSystemSystemLanguage": "Язык прошивки:", "SettingsTabSystemSystemLanguageJapanese": "Японский", "SettingsTabSystemSystemLanguageAmericanEnglish": "Английский (США)", "SettingsTabSystemSystemLanguageFrench": "Французский", @@ -122,19 +127,19 @@ "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Испанский (Латиноамериканский)", "SettingsTabSystemSystemLanguageSimplifiedChinese": "Китайский упрощённый", "SettingsTabSystemSystemLanguageTraditionalChinese": "Китайский традиционный", - "SettingsTabSystemSystemTimeZone": "Часовой пояс системы:", - "SettingsTabSystemSystemTime": "Время системы:", + "SettingsTabSystemSystemTimeZone": "Часовой пояс прошивки:", + "SettingsTabSystemSystemTime": "Время в прошивке:", "SettingsTabSystemEnableVsync": "Включить вертикальную синхронизацию", "SettingsTabSystemEnablePptc": "Включить PPTC (Profiled Persistent Translation Cache)", - "SettingsTabSystemEnableFsIntegrityChecks": "Включить проверку целостности FS", - "SettingsTabSystemAudioBackend": "Аудио бэкэнд:", - "SettingsTabSystemAudioBackendDummy": "Муляж", + "SettingsTabSystemEnableFsIntegrityChecks": "Включить проверку целостности файловой системы", + "SettingsTabSystemAudioBackend": "Аудио бэкенд:", + "SettingsTabSystemAudioBackendDummy": "Без звука", "SettingsTabSystemAudioBackendOpenAL": "OpenAL", "SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSDL2": "SDL2", "SettingsTabSystemHacks": "Хаки", - "SettingsTabSystemHacksNote": " (Эти многие настройки вызывают нестабильность)", - "SettingsTabSystemExpandDramSize": "Увеличение размера DRAM до 6GiB", + "SettingsTabSystemHacksNote": "Возможна нестабильная работа", + "SettingsTabSystemExpandDramSize": "Использовать альтернативный макет памяти (для разработчиков)", "SettingsTabSystemIgnoreMissingServices": "Игнорировать отсутствующие службы", "SettingsTabGraphics": "Графика", "SettingsTabGraphicsAPI": "Графические API", @@ -145,12 +150,12 @@ "SettingsTabGraphicsAnisotropicFiltering4x": "4x", "SettingsTabGraphicsAnisotropicFiltering8x": "8x", "SettingsTabGraphicsAnisotropicFiltering16x": "16x", - "SettingsTabGraphicsResolutionScale": "Масштаб:", + "SettingsTabGraphicsResolutionScale": "Масштабирование:", "SettingsTabGraphicsResolutionScaleCustom": "Пользовательский (не рекомендуется)", - "SettingsTabGraphicsResolutionScaleNative": "Родной (720p/1080p)", + "SettingsTabGraphicsResolutionScaleNative": "Нативное (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (не рекомендуется)", "SettingsTabGraphicsAspectRatio": "Соотношение сторон:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -172,16 +177,16 @@ "SettingsTabLoggingEnableFsAccessLogs": "Включить журналы доступа файловой системы", "SettingsTabLoggingFsGlobalAccessLogMode": "Режим журнала глобального доступа файловой системы:", "SettingsTabLoggingDeveloperOptions": "Параметры разработчика", - "SettingsTabLoggingDeveloperOptionsNote": "ВНИМАНИЕ: Снижает производительность", + "SettingsTabLoggingDeveloperOptionsNote": "ВНИМАНИЕ: эти настройки снижают производительность", "SettingsTabLoggingGraphicsBackendLogLevel": "Уровень журнала бэкенда графики:", - "SettingsTabLoggingGraphicsBackendLogLevelNone": "Ничего", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Нет", "SettingsTabLoggingGraphicsBackendLogLevelError": "Ошибка", "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Замедления", "SettingsTabLoggingGraphicsBackendLogLevelAll": "Всё", "SettingsTabLoggingEnableDebugLogs": "Включить журнал отладки", "SettingsTabInput": "Управление", - "SettingsTabInputEnableDockedMode": "Включить режим закрепления", - "SettingsTabInputDirectKeyboardAccess": "Прямой доступ с клавиатуры", + "SettingsTabInputEnableDockedMode": "Стационарный режим", + "SettingsTabInputDirectKeyboardAccess": "Прямой ввод клавиатуры", "SettingsButtonSave": "Сохранить", "SettingsButtonClose": "Закрыть", "SettingsButtonOk": "Ок", @@ -203,9 +208,9 @@ "ControllerSettingsControllerType": "Тип контроллера", "ControllerSettingsControllerTypeHandheld": "Портативный", "ControllerSettingsControllerTypeProController": "Pro Контроллер", - "ControllerSettingsControllerTypeJoyConPair": "JoyCon Пара", - "ControllerSettingsControllerTypeJoyConLeft": "JoyCon Левый", - "ControllerSettingsControllerTypeJoyConRight": "JoyCon Правый", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon (пара)", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon (левый)", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon (правый)", "ControllerSettingsProfile": "Профиль", "ControllerSettingsProfileDefault": "По умолчанию", "ControllerSettingsLoad": "Загрузить", @@ -218,19 +223,19 @@ "ControllerSettingsButtonY": "Y", "ControllerSettingsButtonPlus": "+", "ControllerSettingsButtonMinus": "-", - "ControllerSettingsDPad": "Направляющая панель", + "ControllerSettingsDPad": "Кнопки направления", "ControllerSettingsDPadUp": "Вверх", "ControllerSettingsDPadDown": "Вниз", "ControllerSettingsDPadLeft": "Влево", "ControllerSettingsDPadRight": "Вправо", - "ControllerSettingsStickButton": "Кнопка", + "ControllerSettingsStickButton": "Нажатие на стик", "ControllerSettingsStickUp": "Вверх", "ControllerSettingsStickDown": "Вниз", "ControllerSettingsStickLeft": "Влево", "ControllerSettingsStickRight": "Вправо", "ControllerSettingsStickStick": "Стик", - "ControllerSettingsStickInvertXAxis": "Инвертировать X ось", - "ControllerSettingsStickInvertYAxis": "Инвертировать Y ось", + "ControllerSettingsStickInvertXAxis": "Инвертировать ось X", + "ControllerSettingsStickInvertYAxis": "Инвертировать ось Y", "ControllerSettingsStickDeadzone": "Мёртвая зона:", "ControllerSettingsLStick": "Левый стик", "ControllerSettingsRStick": "Правый стик", @@ -252,10 +257,10 @@ "ControllerSettingsMisc": "Разное", "ControllerSettingsTriggerThreshold": "Порог срабатывания:", "ControllerSettingsMotion": "Движение", - "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Используйте движение, совместимое с CemuHook", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Включить совместимость с CemuHook", "ControllerSettingsMotionControllerSlot": "Слот контроллера:", "ControllerSettingsMotionMirrorInput": "Зеркальный ввод", - "ControllerSettingsMotionRightJoyConSlot": "Правый JoyCon слот:", + "ControllerSettingsMotionRightJoyConSlot": "Слот правого JoyCon:", "ControllerSettingsMotionServerHost": "Хост сервера:", "ControllerSettingsMotionGyroSensitivity": "Чувствительность гироскопа:", "ControllerSettingsMotionGyroDeadzone": "Мертвая зона гироскопа:", @@ -268,12 +273,12 @@ "UserProfilesAddNewProfile": "Добавить новый профиль", "UserProfilesDelete": "Удалить", "UserProfilesClose": "Закрыть", - "ProfileNameSelectionWatermark": "Выберите псевдоним", + "ProfileNameSelectionWatermark": "Выберите никнейм", "ProfileImageSelectionTitle": "Выбор изображения профиля", "ProfileImageSelectionHeader": "Выберите изображение профиля", "ProfileImageSelectionNote": "Вы можете импортировать собственное изображение профиля или выбрать аватар из системной прошивки.", - "ProfileImageSelectionImportImage": "Импорт файла изображения", - "ProfileImageSelectionSelectAvatar": "Выберите аватар прошивки", + "ProfileImageSelectionImportImage": "Импорт изображения", + "ProfileImageSelectionSelectAvatar": "Выстроенные аватары", "InputDialogTitle": "Диалоговое окно ввода", "InputDialogOk": "ОК", "InputDialogCancel": "Отмена", @@ -290,15 +295,11 @@ "MenuBarFileToolsTakeScreenshot": "Сделать снимок экрана", "MenuBarFileToolsHideUi": "Скрыть UI", "GameListContextMenuRunApplication": "Запуск приложения", - "GameListContextMenuToggleFavorite": "Переключить Избранное", - "GameListContextMenuToggleFavoriteToolTip": "Переключить любимый статус игры", - "SettingsTabGeneralTheme": "Тема", - "SettingsTabGeneralThemeCustomTheme": "Пользовательский путь к теме", - "SettingsTabGeneralThemeBaseStyle": "Базовый стиль", - "SettingsTabGeneralThemeBaseStyleDark": "Тёмная", - "SettingsTabGeneralThemeBaseStyleLight": "Светлая", - "SettingsTabGeneralThemeEnableCustomTheme": "Включить пользовательскую тему", - "ButtonBrowse": "Обзор", + "GameListContextMenuToggleFavorite": "Добавить в избранное", + "GameListContextMenuToggleFavoriteToolTip": "Помечает игру звездочкой как избранную", + "SettingsTabGeneralTheme": "Тема:", + "SettingsTabGeneralThemeDark": "Темная", + "SettingsTabGeneralThemeLight": "Светлая", "ControllerSettingsConfigureGeneral": "Настройка", "ControllerSettingsRumble": "Вибрация", "ControllerSettingsRumbleStrongMultiplier": "Множитель сильной вибрации", @@ -332,8 +333,6 @@ "DialogUpdaterAddingFilesMessage": "Добавление нового обновления...", "DialogUpdaterCompleteMessage": "Обновление завершено!", "DialogUpdaterRestartMessage": "Вы хотите перезапустить Ryujinx сейчас?", - "DialogUpdaterArchNotSupportedMessage": "Вы используете не поддерживаемую системную архитектуру!", - "DialogUpdaterArchNotSupportedSubMessage": "(Поддерживаются только x64 системы)", "DialogUpdaterNoInternetMessage": "Вы не подключены к интернету!", "DialogUpdaterNoInternetSubMessage": "Убедитесь, что у вас есть работающее подключение к интернету!", "DialogUpdaterDirtyBuildMessage": "Вы не можете обновить Dirty Build!", @@ -349,7 +348,7 @@ "DialogInstallFileTypesErrorMessage": "Не удалось установить типы файлов.", "DialogUninstallFileTypesSuccessMessage": "Успешно удалены типы файлов!", "DialogUninstallFileTypesErrorMessage": "Не удалось удалить типы файлов.", - "DialogOpenSettingsWindowLabel": "Открыть окно настроек", + "DialogOpenSettingsWindowLabel": "Открыть окно параметров", "DialogControllerAppletTitle": "Апплет контроллера", "DialogMessageDialogErrorExceptionMessage": "Ошибка отображения диалогового окна сообщений: {0}", "DialogSoftwareKeyboardErrorExceptionMessage": "Ошибка отображения программной клавиатуры: {0}", @@ -373,11 +372,11 @@ "DialogInvalidTitleIdErrorMessage": "Ошибка пользовательского интерфейса: выбранная игра не имеет действительного идентификатора названия.", "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Действительная системная прошивка не найдена в {0}.", "DialogFirmwareInstallerFirmwareInstallTitle": "Установить прошивку {0}", - "DialogFirmwareInstallerFirmwareInstallMessage": "Будет установлена версия системы {0}.", - "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nЭто заменит текущую версию системы {0}.", + "DialogFirmwareInstallerFirmwareInstallMessage": "Будет установлена версия прошивки {0}.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nЭто заменит текущую версию прошивки {0}.", "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nПродолжить?", "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Установка прошивки...", - "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Версия системы {0} успешно установлена.", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Прошивка версии {0} успешно установлена.", "DialogUserProfileDeletionWarningMessage": "Если выбранный профиль будет удален, другие профили не будут открываться.", "DialogUserProfileDeletionConfirmMessage": "Вы хотите удалить выбранный профиль", "DialogUserProfileUnsavedChangesTitle": "Внимание - Несохраненные изменения", @@ -385,7 +384,10 @@ "DialogUserProfileUnsavedChangesSubMessage": "Вы хотите отменить изменения?", "DialogControllerSettingsModifiedConfirmMessage": "Текущие настройки контроллера обновлены.", "DialogControllerSettingsModifiedConfirmSubMessage": "Вы хотите сохранить?", - "DialogLoadNcaErrorMessage": "{0}. Файл с ошибкой: {1}", + "DialogLoadFileErrorMessage": "{0}. Файл с ошибкой: {1}", + "DialogModAlreadyExistsMessage": "Мод уже существует", + "DialogModInvalidMessage": "Выбранная папка не содержит модов", + "DialogModDeleteNoParentMessage": "Невозможно удалить: не удалось найти папку мода \"{0}\"!", "DialogDlcNoDlcErrorMessage": "Указанный файл не содержит DLC для выбранной игры!", "DialogPerformanceCheckLoggingEnabledMessage": "У вас включено ведение журнала отладки, предназначенное только для разработчиков.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Для оптимальной производительности рекомендуется отключить ведение журнала отладки. Вы хотите отключить ведение журнала отладки сейчас?", @@ -396,6 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "Указанный файл не содержит обновления для выбранного заголовка!", "DialogSettingsBackendThreadingWarningTitle": "Предупреждение: многопоточность в бэкенде", "DialogSettingsBackendThreadingWarningMessage": "Ryujinx необходимо перезапустить после изменения этой опции, чтобы она полностью применилась. В зависимости от вашей платформы вам может потребоваться вручную отключить собственную многопоточность вашего драйвера при использовании Ryujinx.", + "DialogModManagerDeletionWarningMessage": "Вы сейчас удалите мод: {0}\n\nВы уверены, что хотите продолжить?", + "DialogModManagerDeletionAllWarningMessage": "Вы сейчас удалите все выбранные файлы для этой игры.\n\nВы уверены, что хотите продолжить?", "SettingsTabGraphicsFeaturesOptions": "Функции & Улучшения", "SettingsTabGraphicsBackendMultithreading": "Многопоточность графического бэкенда:", "CommonAuto": "Автоматически", @@ -406,18 +410,18 @@ "DialogProfileInvalidProfileNameErrorMessage": "Имя файла содержит недопустимые символы. Пожалуйста, попробуйте еще раз.", "MenuBarOptionsPauseEmulation": "Пауза", "MenuBarOptionsResumeEmulation": "Продолжить", - "AboutUrlTooltipMessage": "Нажмите, чтобы открыть веб-сайт Ryujinx в браузере по умолчанию.", + "AboutUrlTooltipMessage": "Нажмите, чтобы открыть веб-сайт Ryujinx", "AboutDisclaimerMessage": "Ryujinx никоим образом не связан ни с Nintendo™, ни с кем-либо из ее партнеров.", - "AboutAmiiboDisclaimerMessage": "Amiibo API (www.amiibo api.com) используется\n нашей эмуляции Amiibo.", - "AboutPatreonUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx Patreon в браузере по умолчанию.", - "AboutGithubUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx GitHub в браузере по умолчанию.", - "AboutDiscordUrlTooltipMessage": "Нажмите, чтобы открыть приглашение на сервер Ryujinx Discord в браузере по умолчанию.", - "AboutTwitterUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx в Twitter в браузере по умолчанию.", + "AboutAmiiboDisclaimerMessage": "Amiibo API (www.amiiboapi.com) используется для эмуляции Amiibo.", + "AboutPatreonUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx на Patreon", + "AboutGithubUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx на GitHub", + "AboutDiscordUrlTooltipMessage": "Нажмите, чтобы открыть приглашение на сервер Ryujinx в Discord", + "AboutTwitterUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx в X (бывший Twitter)", "AboutRyujinxAboutTitle": "О программе:", - "AboutRyujinxAboutContent": "Ryujinx — это эмулятор Nintendo Switch™.\nПожалуйста, поддержите нас на Patreon.\nУзнавайте все последние новости в нашем Twitter или Discord.\nРазработчики, заинтересованные в участии, могут узнать больше на нашем GitHub или в Discord.", - "AboutRyujinxMaintainersTitle": "Поддерживается:", - "AboutRyujinxMaintainersContentTooltipMessage": "Нажмите, чтобы открыть страницу Contributors в браузере по умолчанию.", - "AboutRyujinxSupprtersTitle": "Поддерживается на Patreon:", + "AboutRyujinxAboutContent": "Ryujinx — это эмулятор Nintendo Switch™.\nПожалуйста, поддержите нас на Patreon.\nЧитайте последние новости в наших X (Twitter) или Discord.\nРазработчики, заинтересованные в участии, могут ознакомиться с проектом на GitHub или в Discord.", + "AboutRyujinxMaintainersTitle": "Разработка:", + "AboutRyujinxMaintainersContentTooltipMessage": "Нажмите, чтобы открыть страницу с участниками", + "AboutRyujinxSupprtersTitle": "Поддержка на Patreon:", "AmiiboSeriesLabel": "Серия Amiibo", "AmiiboCharacterLabel": "Персонаж", "AmiiboScanButtonLabel": "Сканировать", @@ -427,13 +431,14 @@ "DlcManagerTableHeadingTitleIdLabel": "Идентификатор заголовка", "DlcManagerTableHeadingContainerPathLabel": "Путь к контейнеру", "DlcManagerTableHeadingFullPathLabel": "Полный путь", - "DlcManagerRemoveAllButton": "Убрать все", + "DlcManagerRemoveAllButton": "Удалить все", "DlcManagerEnableAllButton": "Включить все", "DlcManagerDisableAllButton": "Отключить все", - "MenuBarOptionsChangeLanguage": "Изменить язык", - "MenuBarShowFileTypes": "Показать типы файлов", - "CommonSort": "Сортировать", - "CommonShowNames": "Показать названия", + "ModManagerDeleteAllButton": "Удалить все", + "MenuBarOptionsChangeLanguage": "Сменить язык", + "MenuBarShowFileTypes": "Показывать форматы файлов", + "CommonSort": "Сортировка", + "CommonShowNames": "Показывать названия", "CommonFavorite": "Избранные", "OrderAscending": "По возрастанию", "OrderDescending": "По убыванию", @@ -447,39 +452,39 @@ "CustomThemePathTooltip": "Путь к пользовательской теме интерфейса", "CustomThemeBrowseTooltip": "Просмотр пользовательской темы интерфейса", "DockModeToggleTooltip": "\"Стационарный\" режим запускает эмулятор, как если бы Nintendo Switch находилась в доке, что улучшает графику и разрешение в большинстве игр. И наоборот, при отключении этого режима эмулятор будет запускать игры в \"Портативном\" режиме, снижая качество графики.\n\nНастройте управление для Игрока 1 если планируете использовать в \"Стационарном\" режиме; настройте портативное управление если планируете использовать эмулятор в \"Портативном\" режиме.\n\nОставьте включенным если не уверены.", - "DirectKeyboardTooltip": "Включить или отключить «поддержку прямого доступа к клавиатуре (HID)» (предоставляет играм доступ к клавиатуре как к устройству ввода текста)", - "DirectMouseTooltip": "Включить или отключить «поддержку прямого доступа к мыши (HID)» (предоставляет играм доступ к вашей мыши как указывающему устройству)", - "RegionTooltip": "Изменение региона системы", - "LanguageTooltip": "Изменение языка системы", - "TimezoneTooltip": "Изменение часового пояса системы", + "DirectKeyboardTooltip": "Поддержка прямого ввода с клавиатуры (HID). Предоставляет игре прямой доступ к клавиатуре в качестве устройства ввода текста.\nРаботает только с играми, которые изначально поддерживают использование клавиатуры с Switch.\nРекомендуется оставить выключенным.", + "DirectMouseTooltip": "Поддержка прямого ввода мыши (HID). Предоставляет игре прямой доступ к мыши в качестве указывающего устройства.\nРаботает только с играми, которые изначально поддерживают использование мыши совместно с железом Switch.\nРекомендуется оставить выключенным.", + "RegionTooltip": "Изменение региона прошивки", + "LanguageTooltip": "Изменение языка прошивки", + "TimezoneTooltip": "Изменение часового пояса прошивки", "TimeTooltip": "Изменение системного времени", - "VSyncToggleTooltip": "Эмуляция вертикальной синхронизации консоли, которая ограничивает количество кадров в секунду в большинстве игр; отключение может привести к тому, что игры будут запущены с более высокой частотой кадров, но загрузка игры может занять больше времени, либо игра не запустится вообще.\n\nМожно включать и выключать эту настройку непосредственно в игре с помощью горячих клавиш. Если планируете отключить вериткальную синхронизацию, мы рекомендуем настроить горячие клавиши.\n\nРекомендуется оставить включенным.", + "VSyncToggleTooltip": "Эмуляция вертикальной синхронизации консоли, которая ограничивает количество кадров в секунду в большинстве игр; отключение может привести к тому, что игры будут запущены с более высокой частотой кадров, но загрузка игры может занять больше времени, либо игра не запустится вообще.\n\nМожно включать и выключать эту настройку непосредственно в игре с помощью горячих клавиш (F1 по умолчанию). Если планируете отключить вертикальную синхронизацию, рекомендуем настроить горячие клавиши.\n\nРекомендуется оставить включенным.", "PptcToggleTooltip": "Сохранение преобразованных JIT-функций таким образом, чтобы их не нужно преобразовывать по новой каждый раз при загрузке игры.\n\nУменьшает статтеры и значительно ускоряет последующую загрузку игр.\n\nРекомендуется оставить включенным.", - "FsIntegrityToggleTooltip": "Проверяет поврежденные файлы при загрузке игры и если поврежденные файлы обнаружены, отображает ошибку о поврежденном хэше в журнале.\n\nНе влияет на производительность и необходим для содействия в устранении неполадок.\n\nРекомендуется оставить включенным.", + "FsIntegrityToggleTooltip": "Проверяет поврежденные файлы при загрузке игры и если поврежденные файлы обнаружены, отображает ошибку о поврежденном хэше в журнале.\n\nНе влияет на производительность и необходим для помощи в устранении неполадок.\n\nРекомендуется оставить включенным.", "AudioBackendTooltip": "Изменяет используемый аудио-бэкенд для рендера звука.\n\nSDL2 является предпочтительным, в то время как OpenAL и SoundIO используются в качестве резервных. При выборе \"Заглушки\" звук будет отсутствовать.\n\nРекомендуется использование SDL2.", "MemoryManagerTooltip": "Изменение разметки и доступа к гостевой памяти. Значительно влияет на производительность процессора.\n\nРекомендуется оставить \"Хост не установлен\"", "MemoryManagerSoftwareTooltip": "Использует таблицу страниц для преобразования адресов. Самая высокая точность, но самая низкая производительность.", "MemoryManagerHostTooltip": "Прямая разметка памяти в адресном пространстве хоста. Значительно более быстрая JIT-компиляция и запуск.", - "MemoryManagerUnsafeTooltip": "Производит прямую разметку памяти, но не маскирует адрес в гостевом адресном пространстве перед получением доступа. Быстрее, но менее безопасно. Гостевое приложение может получить доступ к памяти из любой точки Ryujinx, поэтому в этом режиме рекомендуется запускать только те программы, которым вы доверяете.", - "UseHypervisorTooltip": "Использует Гипервизор вместо JIT. Значительно увеличивает производительность, но может быть работать нестабильно.", - "DRamTooltip": "Использует альтернативный макет MemoryMode для имитации использования Nintendo Switch для разработчика.\n\nПолезно только для пакетов текстур с высоким разрешением или модов добавляющих разрешение 4К. Не улучшает производительность.\n\nРекомендуется оставить выключенным.", - "IgnoreMissingServicesTooltip": "Игнорирует нереализованные сервисы Horizon OS. Это поможет избежать вылеты при загрузке определенных игр.\n\nРекомендуется оставить выключенным.", - "GraphicsBackendThreadingTooltip": "Выполняет команды графического бэкенда на втором потоке.\n\nУскоряет компиляцию шейдеров, уменьшает статтеры и повышает производительность на драйверах GPU без поддержки многопоточности. Производительность на драйверах с многопоточностью немного выше.\n\nРекомендуется оставить в Авто.", - "GalThreadingTooltip": "Выполняет команды графического бэкенда на во втором потоке.\n\nУскоряет компиляцию шейдеров, уменьшает статтеры и повышает производительность на драйверах GPU без поддержки многопоточности. Производительность на драйверах с многопоточностью немного выше.\n\nРекомендуется оставить в Авто.", + "MemoryManagerUnsafeTooltip": "Производит прямую разметку памяти, но не маскирует адрес в гостевом адресном пространстве перед получением доступа. Быстро, но небезопасно. Гостевое приложение может получить доступ к памяти из Ryujinx, поэтому в этом режиме рекомендуется запускать только те программы, которым вы доверяете.", + "UseHypervisorTooltip": "Использует Hypervisor вместо JIT. Значительно увеличивает производительность, но может работать нестабильно.", + "DRamTooltip": "Использует альтернативный макет MemoryMode для имитации использования Nintendo Switch в режиме разработчика.\n\nПолезно только для пакетов текстур с высоким разрешением или модов добавляющих разрешение 4К. Не улучшает производительность.\n\nРекомендуется оставить выключенным.", + "IgnoreMissingServicesTooltip": "Игнорирует нереализованные сервисы Horizon OS. Эта настройка поможет избежать вылеты при запуске определенных игр.\n\nРекомендуется оставить выключенным.", + "GraphicsBackendThreadingTooltip": "Выполняет команды графического бэкенда на втором потоке.\n\nУскоряет компиляцию шейдеров, уменьшает статтеры и повышает производительность на драйверах GPU без поддержки многопоточности. Производительность на драйверах с многопоточностью немного выше.\n\nРекомендуется оставить Автоматически.", + "GalThreadingTooltip": "Выполняет команды графического бэкенда на втором потоке.\n\nУскоряет компиляцию шейдеров, уменьшает статтеры и повышает производительность на драйверах GPU без поддержки многопоточности. Производительность на драйверах с многопоточностью немного выше.\n\nРекомендуется оставить в Авто.", "ShaderCacheToggleTooltip": "Сохраняет кэш шейдеров на диске, который уменьшает статтеры при последующих запусках.\n\nРекомендуется оставить включенным.", - "ResolutionScaleTooltip": "Масштабирование разрешения", + "ResolutionScaleTooltip": "Увеличивает разрешение рендера игры.\n\nНекоторые игры могут не работать с этой настройкой и выглядеть смазано даже когда разрешение увеличено; Для этих игр вам может потребоваться найти моды, которые убирают сглаживание или увеличивают разрешение рендеринга этих игр. Для использования последнего, вам нужно будет выбрать опцию \"Нативное\".\n\nЭта опция может быть изменена во время игры по нажатию кнопки \"Применить\" ниже; Вы можете просто переместить окно настроек в сторону и поэкспериментировать, пока не подберете подходящие настройки для конкретной игры.\n\nИмейте в виду, что \"4x\" является излишеством.", "ResolutionScaleEntryTooltip": "Масштабирование разрешения с плавающей запятой, например 1,5. Неинтегральное масштабирование с большой вероятностью вызовет сбои в работе.", - "AnisotropyTooltip": "Уровень анизотропной фильтрации (установите значение «Авто», чтобы использовать значение в игре по умолчанию)", - "AspectRatioTooltip": "Соотношение сторон, применяемое к окну.", + "AnisotropyTooltip": "Уровень анизотропной фильтрации. Установите значение «Авто», чтобы использовать значение в игре по умолчанию.", + "AspectRatioTooltip": "Соотношение сторон применимое к окну рендерера.\n\nИзмените эту настройку только если вы используете мод для соотношения сторон, иначе изображение будет растянуто.\n\nРекомендуется настройка 16:9.", "ShaderDumpPathTooltip": "Путь дампа графических шейдеров", "FileLogTooltip": "Включает или отключает ведение журнала в файл на диске. Не влияет на производительность.", "StubLogTooltip": "Включает ведение журнала-заглушки. Не влияет на производительность.", - "InfoLogTooltip": "Включает печать сообщений информационного журнала. Не влияет на производительность.", - "WarnLogTooltip": "Включает печать сообщений журнала предупреждений. Не влияет на производительность.", - "ErrorLogTooltip": "Включает печать сообщений журнала ошибок. Не влияет на производительность.", + "InfoLogTooltip": "Включает вывод сообщений информационного журнала в консоль. Не влияет на производительность.", + "WarnLogTooltip": "Включает вывод сообщений журнала предупреждений в консоль. Не влияет на производительность.", + "ErrorLogTooltip": "Включает вывод сообщений журнала ошибок. Не влияет на производительность.", "TraceLogTooltip": "Выводит сообщения журнала трассировки в консоли. Не влияет на производительность.", - "GuestLogTooltip": "Включает печать сообщений гостевого журнала. Не влияет на производительность.", - "FileAccessLogTooltip": "Включает печать сообщений журнала доступа к файлам", + "GuestLogTooltip": "Включает вывод сообщений гостевого журнала. Не влияет на производительность.", + "FileAccessLogTooltip": "Включает вывод сообщений журнала доступа к файлам", "FSAccessLogModeTooltip": "Включает вывод журнала доступа к файловой системе. Возможные режимы: 0-3", "DeveloperOptionTooltip": "Используйте с осторожностью", "OpenGlLogLevel": "Требует включения соответствующих уровней ведения журнала", @@ -487,10 +492,10 @@ "LoadApplicationFileTooltip": "Открыть файловый менеджер для выбора файла, совместимого с Nintendo Switch.", "LoadApplicationFolderTooltip": "Открыть файловый менеджер для выбора распакованного приложения, совместимого с Nintendo Switch.", "OpenRyujinxFolderTooltip": "Открывает папку файловой системы Ryujinx. ", - "OpenRyujinxLogsTooltip": "Открывает папку, в которую записываются журналы", + "OpenRyujinxLogsTooltip": "Открывает папку, в которую записываются логи", "ExitTooltip": "Выйти из Ryujinx", - "OpenSettingsTooltip": "Открыть окно настроек", - "OpenProfileManagerTooltip": "Открыть диспетчер профилей", + "OpenSettingsTooltip": "Открыть окно параметров", + "OpenProfileManagerTooltip": "Открыть менеджер учетных записей", "StopEmulationTooltip": "Остановка эмуляции текущей игры и возврат к списку игр", "CheckUpdatesTooltip": "Проверка наличия обновления Ryujinx", "OpenAboutTooltip": "Открыть окно «О программе»", @@ -504,6 +509,8 @@ "EnableInternetAccessTooltip": "Позволяет эмулированному приложению подключаться к Интернету.\n\nПри включении этой функции игры с возможностью сетевой игры могут подключаться друг к другу, если все эмуляторы (или реальные консоли) подключены к одной и той же точке доступа.\n\nНЕ разрешает подключение к серверам Nintendo. Может вызвать сбой в некоторых играх, которые пытаются подключиться к Интернету.\n\nРекомендутеся оставить выключенным.", "GameListContextMenuManageCheatToolTip": "Управление читами", "GameListContextMenuManageCheat": "Управление читами", + "GameListContextMenuManageModToolTip": "Управление модами", + "GameListContextMenuManageMod": "Управление модами", "ControllerSettingsStickRange": "Диапазон:", "DialogStopEmulationTitle": "Ryujinx - Остановить эмуляцию", "DialogStopEmulationMessage": "Вы уверены, что хотите остановить эмуляцию?", @@ -515,10 +522,8 @@ "SettingsTabCpuMemory": "Память ЦП", "DialogUpdaterFlatpakNotSupportedMessage": "Пожалуйста, обновите Ryujinx через FlatHub.", "UpdaterDisabledWarningTitle": "Обновление выключено!", - "GameListContextMenuOpenSdModsDirectory": "Открыть папку с модами Atmosphere", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Открывает альтернативную папку SD-карты Atmosphere, которая содержит моды для приложений и игр. Полезно для модов, сделанных для реальной консоли.", "ControllerSettingsRotate90": "Повернуть на 90° по часовой стрелке", - "IconSize": "Размер иконки", + "IconSize": "Размер обложек", "IconSizeTooltip": "Изменить размер игровых иконок", "MenuBarOptionsShowConsole": "Показать консоль", "ShaderCachePurgeError": "Ошибка очистки кэша шейдеров в {0}: {1}", @@ -535,7 +540,7 @@ "UserErrorUnknownDescription": "Произошла неизвестная ошибка!", "UserErrorUndefinedDescription": "Произошла неизвестная ошибка! Такого не должно происходить. Пожалуйста, свяжитесь с разработчиками!", "OpenSetupGuideMessage": "Открыть руководство по установке", - "NoUpdate": "Нет обновлений", + "NoUpdate": "Без обновлений", "TitleUpdateVersionLabel": "Version {0} - {1}", "RyujinxInfo": "Ryujinx - Информация", "RyujinxConfirm": "Ryujinx - Подтверждение", @@ -544,12 +549,13 @@ "SwkbdMinCharacters": "Должно быть не менее {0} символов.", "SwkbdMinRangeCharacters": "Должно быть {0}-{1} символов", "SoftwareKeyboard": "Программная клавиатура", - "SoftwareKeyboardModeNumbersOnly": "Должны быть только цифры", + "SoftwareKeyboardModeNumeric": "Должно быть в диапазоне 0-9 или '.'", "SoftwareKeyboardModeAlphabet": "Не должно быть CJK-символов", "SoftwareKeyboardModeASCII": "Текст должен быть только в ASCII кодировке", - "DialogControllerAppletMessagePlayerRange": "Приложение запрашивает {0} игроков с:\n\nТИПЫ: {1}\n\nИГРОКИ: {2}\n\n{3}Пожалуйста, откройте \"Настройки\" и перенастройте сейчас или нажмите \"Закрыть\".", - "DialogControllerAppletMessage": "Приложение запрашивает ровно {0} игроков с:\n\nТИПЫ: {1}\n\nИГРОКИ: {2}\n\n{3}Пожалуйста, откройте \"Настройки\" и перенастройте Ввод или нажмите \"Закрыть\".", - "DialogControllerAppletDockModeSet": "Установлен стационарный режим. Портативный режим недоступен.", + "ControllerAppletControllers": "Поддерживаемые геймпады:", + "ControllerAppletPlayers": "Игроки:", + "ControllerAppletDescription": "Текущая конфигурация некорректна. Откройте параметры и перенастройте управление.", + "ControllerAppletDocked": "Используется стационарный режим. Управление в портативном режиме должно быть отключено.", "UpdaterRenaming": "Переименование старых файлов...", "UpdaterRenameFailed": "Программе обновления не удалось переименовать файл: {0}", "UpdaterAddingFiles": "Добавление новых файлов...", @@ -559,7 +565,7 @@ "Docked": "Стационарный режим", "Handheld": "Портативный режим", "ConnectionError": "Ошибка соединения!", - "AboutPageDeveloperListMore": "{0} и больше...", + "AboutPageDeveloperListMore": "{0} и другие...", "ApiError": "Ошибка API.", "LoadingHeading": "Загрузка {0}", "CompilingPPTC": "Компиляция PTC", @@ -571,11 +577,11 @@ "RyujinxUpdater": "Ryujinx - Обновление", "SettingsTabHotkeys": "Горячие клавиши", "SettingsTabHotkeysHotkeys": "Горячие клавиши", - "SettingsTabHotkeysToggleVsyncHotkey": "Переключить VSync:", + "SettingsTabHotkeysToggleVsyncHotkey": "Вкл./выкл. VSync:", "SettingsTabHotkeysScreenshotHotkey": "Скриншот:", "SettingsTabHotkeysShowUiHotkey": "Показать UI:", "SettingsTabHotkeysPauseHotkey": "Пауза:", - "SettingsTabHotkeysToggleMuteHotkey": "Приглушить:", + "SettingsTabHotkeysToggleMuteHotkey": "Выключить звук:", "ControllerMotionTitle": "Настройки управления движением", "ControllerRumbleTitle": "Настройки вибрации", "SettingsSelectThemeFileDialogTitle": "Выбрать файл темы", @@ -587,17 +593,20 @@ "Writable": "Доступно для записи", "SelectDlcDialogTitle": "Выберите файлы DLC", "SelectUpdateDialogTitle": "Выберите файлы обновления", - "UserProfileWindowTitle": "Менеджер профилей пользователей", + "SelectModDialogTitle": "Выбрать папку с модами", + "UserProfileWindowTitle": "Менеджер учетных записей", "CheatWindowTitle": "Менеджер читов", - "DlcWindowTitle": "Управление загружаемым контентом для {0} ({1})", - "UpdateWindowTitle": "Менеджер обновления названий", - "CheatWindowHeading": "Читы доступны для {0} [{1}]", + "DlcWindowTitle": "Управление DLC для {0} ({1})", + "UpdateWindowTitle": "Менеджер обновлений игр", + "CheatWindowHeading": "Доступные читы для {0} [{1}]", "BuildId": "ID версии:", - "DlcWindowHeading": "{0} Загружаемый контент", + "DlcWindowHeading": "{0} DLC", + "ModWindowHeading": "Мод(ы) {0} ", "UserProfilesEditProfile": "Изменить выбранные", "Cancel": "Отмена", "Save": "Сохранить", "Discard": "Отменить", + "Paused": "Приостановлено", "UserProfilesSetProfileImage": "Установить изображение профиля", "UserProfileEmptyNameError": "Имя обязательно", "UserProfileNoImageError": "Изображение профиля должно быть установлено", @@ -606,12 +615,12 @@ "SettingsTabHotkeysResScaleDownHotkey": "Уменьшить разрешение:", "UserProfilesName": "Имя:", "UserProfilesUserId": "ID пользователя:", - "SettingsTabGraphicsBackend": "Бэкенд графики", - "SettingsTabGraphicsBackendTooltip": "Использовать графический бэкенд", + "SettingsTabGraphicsBackend": "Графический бэкенд", + "SettingsTabGraphicsBackendTooltip": "Выберите бэкенд, который будет использован в эмуляторе.\n\nVulkan является лучшим выбором для всех современных графических карт с актуальными драйверами. В Vulkan также включена более быстрая компиляция шейдеров (меньше статтеров) для всех GPU.\n\nOpenGL может достичь лучших результатов на старых GPU Nvidia, на старых GPU AMD на Linux или на GPU с небольшим количеством VRAM, хотя статтеров при компиляции шейдеров будут больше.\n\nРекомендуется использовать Vulkan. Используйте OpenGL, если ваш GPU не поддерживает Vulkan даже с актуальными драйверами.", "SettingsEnableTextureRecompression": "Включить пережатие текстур", - "SettingsEnableTextureRecompressionTooltip": "Сжимает некоторые текстуры для уменьшения использования видеопамяти.\n\nРекомендуется для GPU с 4 гб видеопамяти и менее.\n\nРекомендуется оставить выключенным.", + "SettingsEnableTextureRecompressionTooltip": "Сжатие ASTC текстур для уменьшения использования VRAM. \n\nИгры, использующие этот формат текстур, включая Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder и The Legend of Zelda: Tears of the Kingdom. \n\nНа GPU с 4GiB VRAM или менее возможны вылеты при запуске этих игр. \n\nВключите, только если у вас заканчивается VRAM в вышеупомянутых играх. Рекомендуется оставить выключенным.", "SettingsTabGraphicsPreferredGpu": "Предпочтительный GPU", - "SettingsTabGraphicsPreferredGpuTooltip": "Выберите видеокарту, которая будет использоваться с графическим бэкендом Vulkan.\n\nНе влияет на GPU, который будет использовать OpenGL.\n\nУстановите графический процессор, помеченный как \"dGPU\", если вы не уверены. Если его нет, оставьте нетронутым.", + "SettingsTabGraphicsPreferredGpuTooltip": "Выберите графический процессор, которая будет использоваться с графическим бэкендом Vulkan.\n\nЭта настройка не влияет на графический процессор, который будет использовать OpenGL.\n\nЕсли вы не уверены что нужно выбрать, используйте графический процессор, помеченный как \"dGPU\". Если его нет, оставьте выбор по умолчанию.", "SettingsAppRequiredRestartMessage": "Требуется перезапуск Ryujinx", "SettingsGpuBackendRestartMessage": "Графический бэкенд или настройки графического процессора были изменены. Требуется перезапуск для вступления в силу изменений.", "SettingsGpuBackendRestartSubMessage": "Перезапустить сейчас?", @@ -621,7 +630,7 @@ "SettingsEnableMacroHLE": "Включить Macro HLE", "SettingsEnableMacroHLETooltip": "Высокоуровневая эмуляции макроса GPU.\n\nПовышает производительность, но может вызывать графические сбои в некоторых играх.\n\nРекомендуется оставить включенным.", "SettingsEnableColorSpacePassthrough": "Пропуск цветового пространства", - "SettingsEnableColorSpacePassthroughTooltip": "Направляет бэкэнд Vulkan на передачу информации о цвете без указания цветового пространства. Для пользователей с экранами с расширенной гаммой данная настройка приводит к получению более ярких цветов за счет снижения корректности цветопередачи.", + "SettingsEnableColorSpacePassthroughTooltip": "Направляет бэкенд Vulkan на передачу информации о цвете без указания цветового пространства. Для пользователей с экранами с расширенной гаммой данная настройка приводит к получению более ярких цветов за счет снижения корректности цветопередачи.", "VolumeShort": "Громкость", "UserProfilesManageSaves": "Управление сохранениями", "DeleteUserSave": "Вы хотите удалить сохранение пользователя для этой игры?", @@ -631,16 +640,16 @@ "Name": "Название", "Size": "Размер", "Search": "Поиск", - "UserProfilesRecoverLostAccounts": "Восстановить утерянные аккаунты", + "UserProfilesRecoverLostAccounts": "Восстановить учетные записи", "Recover": "Восстановление", "UserProfilesRecoverHeading": "Были найдены сохранения для следующих аккаунтов", - "UserProfilesRecoverEmptyList": "Нет профилей для восстановления", - "GraphicsAATooltip": "Применяет сглаживание к рейдеру игры.", + "UserProfilesRecoverEmptyList": "Нет учетных записей для восстановления", + "GraphicsAATooltip": "Применимое сглаживание для рендера.\n\nFXAA размывает большую часть изображения, SMAA попытается найти \"зазубренные\" края и сгладить их.\n\nНе рекомендуется использовать вместе с масштабирующим фильтром FSR.\n\nЭта опция может быть изменена во время игры по нажатию \"Применить\" ниже; Вы можете просто переместить окно настроек в сторону и поэкспериментировать, пока не найдёте подходящую настройку игры.\n\nРекомендуется использовать \"Нет\".", "GraphicsAALabel": "Сглаживание:", - "GraphicsScalingFilterLabel": "Масштабирующий фильтр:", - "GraphicsScalingFilterTooltip": "Включает масштабирование кадрового буфера", + "GraphicsScalingFilterLabel": "Интерполяция:", + "GraphicsScalingFilterTooltip": "Выберите фильтрацию, которая будет применяться при масштабировании.\n\n\"Билинейная\" хорошо работает для 3D-игр и является настройкой по умолчанию.\n\n\"Точный\" рекомендуется для пиксельных .\n\n\"FSR\" это фильтр резкости, который не рекомендуется использовать с FXAA или SMAA.\n\nЭта опция может быть изменена во время игры по нажатию кнопки \"Применить\" ниже; Вы можете просто переместить окно настроек в сторону и поэкспериментировать, пока не подберете подходящие настройки для конкретной игры.\n\nРекомендуется использовать \"Билинейная\".", "GraphicsScalingFilterLevelLabel": "Уровень", - "GraphicsScalingFilterLevelTooltip": "Установить уровень фильтра масштабирования", + "GraphicsScalingFilterLevelTooltip": "Выбор режима работы FSR 1.0. Выше - четче.", "SmaaLow": "SMAA Низкое", "SmaaMedium": "SMAA Среднее", "SmaaHigh": "SMAA Высокое", @@ -648,9 +657,12 @@ "UserEditorTitle": "Редактирование пользователя", "UserEditorTitleCreate": "Создание пользователя", "SettingsTabNetworkInterface": "Сетевой Интерфейс", - "NetworkInterfaceTooltip": "Сетевой интерфейс, используемый для функций LAN", + "NetworkInterfaceTooltip": "Сетевой интерфейс, используемый для функций LAN/LDN.\n\nВ сочетании с VPN или XLink Kai и игрой с поддержкой LAN, может использоваться для создания локальной сети через интернет.\n\nРекомендуется использовать \"По умолчанию\".", "NetworkInterfaceDefault": "По умолчанию", "PackagingShaders": "Упаковка шейдеров", "AboutChangelogButton": "Список изменений на GitHub", - "AboutChangelogButtonTooltipMessage": "Нажмите, чтобы открыть список изменений для этой версии в браузере." -} \ No newline at end of file + "AboutChangelogButtonTooltipMessage": "Нажмите, чтобы открыть список изменений для этой версии", + "SettingsTabNetworkMultiplayer": "Мультиплеер", + "MultiplayerMode": "Режим:", + "MultiplayerModeTooltip": "Изменение многопользовательского режима LDN.\n\nLdnMitm модифицирует функциональность локальной беспроводной/игры на одном устройстве в играх, позволяя играть с другими пользователями Ryujinx или взломанными консолями Nintendo Switch с установленным модулем ldn_mitm, находящимися в одной локальной сети друг с другом.\n\nМногопользовательская игра требует наличия у всех игроков одной и той же версии игры (т.е. Super Smash Bros. Ultimate v13.0.1 не может подключиться к v13.0.0).\n\nРекомендуется оставить отключенным." +} diff --git a/src/Ryujinx/Assets/Locales/th_TH.json b/src/Ryujinx/Assets/Locales/th_TH.json new file mode 100644 index 0000000000..61f50c7c2e --- /dev/null +++ b/src/Ryujinx/Assets/Locales/th_TH.json @@ -0,0 +1,668 @@ +{ + "Language": "ภาษาไทย", + "MenuBarFileOpenApplet": "เปิด Applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "เปิด Mii Editor Applet ในโหมดสแตนด์อโลน", + "SettingsTabInputDirectMouseAccess": "เข้าถึงเมาส์ได้โดยตรง", + "SettingsTabSystemMemoryManagerMode": "โหมดจัดการหน่วยความจำ:", + "SettingsTabSystemMemoryManagerModeSoftware": "ซอฟต์แวร์", + "SettingsTabSystemMemoryManagerModeHost": "โฮสต์ (เร็ว)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "ไม่ได้ตรวจสอบโฮสต์ (เร็วที่สุด, แต่ไม่ปลอดภัย)", + "SettingsTabSystemUseHypervisor": "ใช้งาน Hypervisor", + "MenuBarFile": "ไฟล์", + "MenuBarFileOpenFromFile": "โหลดแอปพลิเคชั่นจากไฟล์", + "MenuBarFileOpenUnpacked": "โหลดเกมที่คลายแพ็กแล้ว", + "MenuBarFileOpenEmuFolder": "เปิดโฟลเดอร์ รียูจินซ์", + "MenuBarFileOpenLogsFolder": "เปิดโฟลเดอร์ Logs", + "MenuBarFileExit": "ออก", + "MenuBarOptions": "_ตัวเลือก", + "MenuBarOptionsToggleFullscreen": "สลับการแสดงผลแบบเต็มหน้าจอ", + "MenuBarOptionsStartGamesInFullscreen": "เริ่มเกมในโหมดเต็มหน้าจอ", + "MenuBarOptionsStopEmulation": "หยุดการจำลอง", + "MenuBarOptionsSettings": "การตั้งค่า", + "MenuBarOptionsManageUserProfiles": "จัดการโปรไฟล์ผู้ใช้งาน", + "MenuBarActions": "การดำเนินการ", + "MenuBarOptionsSimulateWakeUpMessage": "จำลองข้อความปลุก", + "MenuBarActionsScanAmiibo": "สแกนหา อะมิโบ", + "MenuBarTools": "_เครื่องมือ", + "MenuBarToolsInstallFirmware": "ติดตั้งเฟิร์มแวร์", + "MenuBarFileToolsInstallFirmwareFromFile": "ติดตั้งเฟิร์มแวร์จาก ไฟล์ XCI หรือ ไฟล์ ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "ติดตั้งเฟิร์มแวร์จากไดเร็กทอรี", + "MenuBarToolsManageFileTypes": "จัดการประเภทไฟล์", + "MenuBarToolsInstallFileTypes": "ติดตั้งประเภทไฟล์", + "MenuBarToolsUninstallFileTypes": "ถอนการติดตั้งประเภทไฟล์", + "MenuBarHelp": "_ช่วยเหลือ", + "MenuBarHelpCheckForUpdates": "ตรวจหาการอัพเดต", + "MenuBarHelpAbout": "เกี่ยวกับ", + "MenuSearch": "กำลังค้นหา...", + "GameListHeaderFavorite": "ชื่นชอบ", + "GameListHeaderIcon": "ไอคอน", + "GameListHeaderApplication": "ชื่อ", + "GameListHeaderDeveloper": "ผู้พัฒนา", + "GameListHeaderVersion": "เวอร์ชั่น", + "GameListHeaderTimePlayed": "เวลาที่เล่นไปแล้ว", + "GameListHeaderLastPlayed": "เล่นล่าสุด", + "GameListHeaderFileExtension": "นามสกุลไฟล์", + "GameListHeaderFileSize": "ขนาดไฟล์", + "GameListHeaderPath": "ที่เก็บไฟล์", + "GameListContextMenuOpenUserSaveDirectory": "เปิดไดเร็กทอรี่บันทึกของผู้ใช้", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "เปิดไดเร็กทอรี่ซึ่งมีการบันทึกผู้ใช้ของแอปพลิเคชัน", + "GameListContextMenuOpenDeviceSaveDirectory": "เปิดไดเร็กทอรีบันทึกของอุปกรณ์", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "เปิดไดเรกทอรี่ซึ่งมีบันทึกอุปกรณ์ของแอปพลิเคชัน", + "GameListContextMenuOpenBcatSaveDirectory": "เปิดไดเรกทอรีบันทึก BCAT", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "เปิดไดเรกทอรี่ซึ่งมีการบันทึก BCAT ของแอปพลิเคชัน", + "GameListContextMenuManageTitleUpdates": "จัดการ การอัปเดตหัวข้อ", + "GameListContextMenuManageTitleUpdatesToolTip": "เปิดหน้าต่างการจัดการการอัพเดตหัวข้อ", + "GameListContextMenuManageDlc": "จัดการ DLC", + "GameListContextMenuManageDlcToolTip": "เปิดหน้าต่างการจัดการ DLC", + "GameListContextMenuCacheManagement": "การบริหารจัดการแคช", + "GameListContextMenuCacheManagementPurgePptc": "เพิ่มเข้าคิวงาน PPTC ที่สร้างใหม่", + "GameListContextMenuCacheManagementPurgePptcToolTip": "ทริกเกอร์ PPTC ให้สร้างใหม่ในเวลาบูตเมื่อเปิดตัวเกมครั้งถัดไป", + "GameListContextMenuCacheManagementPurgeShaderCache": "ล้าง เชเดอร์แคช", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "ลบ เชเดอร์แคช ของแอปพลิเคชัน", + "GameListContextMenuCacheManagementOpenPptcDirectory": "เปิดไดเรกทอรี่่ PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "เปิดไดเร็กทอรีที่มี PPTC แคช ของแอปพลิเคชัน", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "เปิดไดเรกทอรี่ เชเดอร์แคช", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "เปิดไดเรกทอรี่ที่มี แคชเชเดอร์ ของแอปพลิเคชัน", + "GameListContextMenuExtractData": "แยกข้อมูล", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "แยกส่วน ExeFS ออกจากการกำหนดค่าปัจจุบันของแอปพลิเคชัน (รวมถึงการอัปเดต)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "แยกส่วน RomFS ออกจากการกำหนดค่าปัจจุบันของแอปพลิเคชัน (รวมถึงการอัพเดต)", + "GameListContextMenuExtractDataLogo": "โลโก้", + "GameListContextMenuExtractDataLogoToolTip": "แยกส่วน โลโก้ ออกจากการกำหนดค่าปัจจุบันของแอปพลิเคชัน (รวมถึงการอัปเดต)", + "GameListContextMenuCreateShortcut": "สร้างทางลัดของแอปพลิเคชัน", + "GameListContextMenuCreateShortcutToolTip": "สร้างทางลัดบนเดสก์ท็อปที่เรียกใช้แอปพลิเคชันที่เลือก", + "GameListContextMenuCreateShortcutToolTipMacOS": "สร้างทางลัดในโฟลเดอร์ Applications ของ macOS ที่เรียกใช้ Application ที่เลือก", + "GameListContextMenuOpenModsDirectory": "เปิดไดเรกทอรี่ Mods", + "GameListContextMenuOpenModsDirectoryToolTip": "เปิดไดเร็กทอรีซึ่งมี Mods ของแอปพลิเคชัน", + "GameListContextMenuOpenSdModsDirectory": "เปิดไดเร็กทอรี Mods Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "เปิดไดเร็กทอรี Atmosphere ของการ์ด SD สำรองซึ่งมี Mods ของแอปพลิเคชัน มีประโยชน์สำหรับ mods ที่บรรจุมากับฮาร์ดแวร์จริง", + "StatusBarGamesLoaded": "เกมส์โหลดแล้ว {0}/{1}", + "StatusBarSystemVersion": "เวอร์ชั่นของระบบ: {0}", + "LinuxVmMaxMapCountDialogTitle": "ตรวจพบขีดจำกัดต่ำสุดสำหรับการแมปหน่วยความจำ", + "LinuxVmMaxMapCountDialogTextPrimary": "คุณต้องการที่จะเพิ่มค่า vm.max_map_count ไปยัง {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "บางเกมอาจพยายามสร้างการแมปหน่วยความจำมากกว่าที่ได้รับอนุญาตในปัจจุบัน รียูจินซ์ จะปิดตัวลงเมื่อเกินขีดจำกัดนี้", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "ใช่, จนกว่าจะรีสตาร์ทครั้งถัดไป", + "LinuxVmMaxMapCountDialogButtonPersistent": "ใช่, อย่างถาวร", + "LinuxVmMaxMapCountWarningTextPrimary": "จำนวนสูงสุดของการแม็ปหน่วยความจำ ต่ำกว่าที่แนะนำ", + "LinuxVmMaxMapCountWarningTextSecondary": "ค่าปัจจุบันของ vm.max_map_count ({0}) มีค่าต่ำกว่า {1} บางเกมอาจพยายามสร้างการแมปหน่วยความจำมากกว่าที่ได้รับอนุญาตในปัจจุบัน รียูจินซ์ จะปิดตัวลงเมื่อเกินขีดจำกัดนี้\n\nคุณอาจต้องการเพิ่มขีดจำกัดด้วยตนเองหรือติดตั้ง pkexec ซึ่งอนุญาตให้ ริวจินซ์ เพื่อช่วยเหลือคุณได้", + "Settings": "การตั้งค่า", + "SettingsTabGeneral": "หน้าจอผู้ใช้", + "SettingsTabGeneralGeneral": "ทั่วไป", + "SettingsTabGeneralEnableDiscordRichPresence": "เปิดใช้งาน Discord Rich Presence", + "SettingsTabGeneralCheckUpdatesOnLaunch": "ตรวจหาการอัปเดตเมื่อเปิดโปรแกรม", + "SettingsTabGeneralShowConfirmExitDialog": "แสดง \"ยืนยันการออก\" กล่องข้อความโต้ตอบ", + "SettingsTabGeneralHideCursor": "ซ่อน เคอร์เซอร์:", + "SettingsTabGeneralHideCursorNever": "ไม่มี", + "SettingsTabGeneralHideCursorOnIdle": "ซ่อนอัตโนมัติ", + "SettingsTabGeneralHideCursorAlways": "ตลอดเวลา", + "SettingsTabGeneralGameDirectories": "ไดเรกทอรี่ของเกม", + "SettingsTabGeneralAdd": "เพิ่ม", + "SettingsTabGeneralRemove": "เอาออก", + "SettingsTabSystem": "ระบบ", + "SettingsTabSystemCore": "แกนกลาง", + "SettingsTabSystemSystemRegion": "ภูมิภาคของระบบ:", + "SettingsTabSystemSystemRegionJapan": "ญี่ปุ่น", + "SettingsTabSystemSystemRegionUSA": "สหรัฐอเมริกา", + "SettingsTabSystemSystemRegionEurope": "ยุโรป", + "SettingsTabSystemSystemRegionAustralia": "ออสเตรเลีย", + "SettingsTabSystemSystemRegionChina": "จีน", + "SettingsTabSystemSystemRegionKorea": "เกาหลี", + "SettingsTabSystemSystemRegionTaiwan": "ไต้หวัน", + "SettingsTabSystemSystemLanguage": "ภาษาของระบบ:", + "SettingsTabSystemSystemLanguageJapanese": "ญี่ปุ่น", + "SettingsTabSystemSystemLanguageAmericanEnglish": "อังกฤษ (อเมริกัน)", + "SettingsTabSystemSystemLanguageFrench": "ฝรั่งเศส", + "SettingsTabSystemSystemLanguageGerman": "เยอรมัน", + "SettingsTabSystemSystemLanguageItalian": "อิตาลี", + "SettingsTabSystemSystemLanguageSpanish": "สเปน", + "SettingsTabSystemSystemLanguageChinese": "จีน", + "SettingsTabSystemSystemLanguageKorean": "เกาหลี", + "SettingsTabSystemSystemLanguageDutch": "ดัตช์", + "SettingsTabSystemSystemLanguagePortuguese": "โปรตุเกส", + "SettingsTabSystemSystemLanguageRussian": "รัสเซีย", + "SettingsTabSystemSystemLanguageTaiwanese": "จีนตัวเต็ม (ไต้หวัน)", + "SettingsTabSystemSystemLanguageBritishEnglish": "อังกฤษ (บริติช)", + "SettingsTabSystemSystemLanguageCanadianFrench": "ฝรั่งเศส (แคนาดา)", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "สเปน (ลาตินอเมริกา)", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "จีน (ตัวย่อ)", + "SettingsTabSystemSystemLanguageTraditionalChinese": "จีน (ดั้งเดิม)", + "SettingsTabSystemSystemTimeZone": "โซนเวลาของระบบ:", + "SettingsTabSystemSystemTime": "เวลาของระบบ:", + "SettingsTabSystemEnableVsync": "VSync", + "SettingsTabSystemEnablePptc": "PPTC (แคชการแปลแบบถาวรที่มีโปรไฟล์)", + "SettingsTabSystemEnableFsIntegrityChecks": "การตรวจสอบความถูกต้องของ FS", + "SettingsTabSystemAudioBackend": "แบ็กเอนด์เสียง:", + "SettingsTabSystemAudioBackendDummy": "ดัมมี่", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "แฮ็ก", + "SettingsTabSystemHacksNote": "อาจทำให้เกิดข้อผิดพลาดได้", + "SettingsTabSystemExpandDramSize": "ใช้รูปแบบหน่วยความจำสำรอง (โหมดนักพัฒนา)", + "SettingsTabSystemIgnoreMissingServices": "ไม่สนใจบริการที่ขาดหายไป", + "SettingsTabGraphics": "กราฟิก", + "SettingsTabGraphicsAPI": "เอพีไอของกราฟิก", + "SettingsTabGraphicsEnableShaderCache": "เปิดใช้งาน เชเดอร์ แคช", + "SettingsTabGraphicsAnisotropicFiltering": "ตัวกรองแบบ แอนไอโซทรอปิก:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "อัตโนมัติ", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "อัตราส่วนความละเอียด:", + "SettingsTabGraphicsResolutionScaleCustom": "กำหนดเอง (ไม่แนะนำ)", + "SettingsTabGraphicsResolutionScaleNative": "พื้นฐานของระบบ (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (ไม่แนะนำ)", + "SettingsTabGraphicsAspectRatio": "อัตราส่วนภาพ:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "ยืดภาพเพื่อให้พอดีกับหน้าต่าง", + "SettingsTabGraphicsDeveloperOptions": "ตัวเลือกนักพัฒนา", + "SettingsTabGraphicsShaderDumpPath": "ที่เก็บไฟล์ดัมพ์ของ เชเดอร์กราฟิก:", + "SettingsTabLogging": "การบันทึก", + "SettingsTabLoggingLogging": "การบันทึก", + "SettingsTabLoggingEnableLoggingToFile": "เปิดใช้งาน การบันทึกไปยังไฟล์", + "SettingsTabLoggingEnableStubLogs": "เปิดใช้งาน บันทึกของต้นขั้ว", + "SettingsTabLoggingEnableInfoLogs": "เปิดใช้งาน บันทึกของข้อมูล", + "SettingsTabLoggingEnableWarningLogs": "เปิดใช้งาน บันทึกคำเตือน", + "SettingsTabLoggingEnableErrorLogs": "เปิดใช้งาน บันทึกข้อผิดพลาด", + "SettingsTabLoggingEnableTraceLogs": "เปิดใช้งาน บันทึกการติดตาม", + "SettingsTabLoggingEnableGuestLogs": "เปิดใช้งาน บันทึกของผู้เยี่ยมชม", + "SettingsTabLoggingEnableFsAccessLogs": "เปิดใช้งาน บันทึกการเข้าถึง Fs", + "SettingsTabLoggingFsGlobalAccessLogMode": "โหมดบันทึกการเข้าถึงส่วนกลาง:", + "SettingsTabLoggingDeveloperOptions": "ตัวเลือกนักพัฒนา", + "SettingsTabLoggingDeveloperOptionsNote": "คำเตือน: จะทำให้ประสิทธิภาพลดลง", + "SettingsTabLoggingGraphicsBackendLogLevel": "ระดับการบันทึก แบ็กเอนด์กราฟิก:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "ไม่มี", + "SettingsTabLoggingGraphicsBackendLogLevelError": "ผิดพลาด", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "ชะลอตัว", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "ทั้งหมด", + "SettingsTabLoggingEnableDebugLogs": "เปิดใช้งานบันทึกการแก้ไขข้อบกพร่อง", + "SettingsTabInput": "ป้อนข้อมูล", + "SettingsTabInputEnableDockedMode": "ด็อกโหมด", + "SettingsTabInputDirectKeyboardAccess": "เข้าถึงคีย์บอร์ดโดยตรง", + "SettingsButtonSave": "บันทึก", + "SettingsButtonClose": "ปิด", + "SettingsButtonOk": "ตกลง", + "SettingsButtonCancel": "ยกเลิก", + "SettingsButtonApply": "นำไปใช้", + "ControllerSettingsPlayer": "ผู้เล่น", + "ControllerSettingsPlayer1": "ผู้เล่นคนที่ 1", + "ControllerSettingsPlayer2": "ผู้เล่นคนที่ 2", + "ControllerSettingsPlayer3": "ผู้เล่นคนที่ 3", + "ControllerSettingsPlayer4": "ผู้เล่นคนที่ 4", + "ControllerSettingsPlayer5": "ผู้เล่นคนที่ 5", + "ControllerSettingsPlayer6": "ผู้เล่นคนที่ 6", + "ControllerSettingsPlayer7": "ผู้เล่นคนที่ 7", + "ControllerSettingsPlayer8": "ผู้เล่นคนที่ 8", + "ControllerSettingsHandheld": "แฮนด์เฮลด์โหมด", + "ControllerSettingsInputDevice": "อุปกรณ์ป้อนข้อมูล", + "ControllerSettingsRefresh": "รีเฟรช", + "ControllerSettingsDeviceDisabled": "ปิดการใช้งาน", + "ControllerSettingsControllerType": "ประเภทของคอนโทรลเลอร์", + "ControllerSettingsControllerTypeHandheld": "แฮนด์เฮลด์", + "ControllerSettingsControllerTypeProController": "โปรคอนโทรลเลอร์", + "ControllerSettingsControllerTypeJoyConPair": "จับคู่ จอยคอน", + "ControllerSettingsControllerTypeJoyConLeft": "จอยคอน ด้านซ้าย", + "ControllerSettingsControllerTypeJoyConRight": "จอยคอน ด้านขวา", + "ControllerSettingsProfile": "โปรไฟล์", + "ControllerSettingsProfileDefault": "ค่าเริ่มต้น", + "ControllerSettingsLoad": "โหลด", + "ControllerSettingsAdd": "เพิ่ม", + "ControllerSettingsRemove": "เอาออก", + "ControllerSettingsButtons": "ปุ่มกด", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "ปุ่มลูกศร", + "ControllerSettingsDPadUp": "ขึ้น", + "ControllerSettingsDPadDown": "ลง", + "ControllerSettingsDPadLeft": "ซ้าย", + "ControllerSettingsDPadRight": "ขวา", + "ControllerSettingsStickButton": "ปุ่ม", + "ControllerSettingsStickUp": "ขึ้น", + "ControllerSettingsStickDown": "ลง", + "ControllerSettingsStickLeft": "ซ้าย", + "ControllerSettingsStickRight": "ขวา", + "ControllerSettingsStickStick": "จอยสติ๊ก", + "ControllerSettingsStickInvertXAxis": "กลับทิศทางของแกน X", + "ControllerSettingsStickInvertYAxis": "กลับทิศทางของแกน Y", + "ControllerSettingsStickDeadzone": "โซนที่ไม่ทำงานของ จอยสติ๊ก:", + "ControllerSettingsLStick": "จอยสติ๊ก ด้านซ้าย", + "ControllerSettingsRStick": "จอยสติ๊ก ด้านขวา", + "ControllerSettingsTriggersLeft": "ทริกเกอร์ ด้านซ้าย", + "ControllerSettingsTriggersRight": "ทริกเกอร์ ด้านขวา", + "ControllerSettingsTriggersButtonsLeft": "ปุ่มทริกเกอร์ ด้านซ้าย", + "ControllerSettingsTriggersButtonsRight": "ปุ่มทริกเกอร์ ด้านขวา", + "ControllerSettingsTriggers": "ทริกเกอร์", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "ปุ่มกดเสริม ด้านซ้าย", + "ControllerSettingsExtraButtonsRight": "ปุ่มกดเสริม ด้านขวา", + "ControllerSettingsMisc": "การควบคุมเพิ่มเติม", + "ControllerSettingsTriggerThreshold": "ตั้งค่าขีดจำกัดของ ทริกเกอร์:", + "ControllerSettingsMotion": "การเคลื่อนไหว", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "ใช้การเคลื่อนไหวที่เข้ากันได้กับ CemuHook", + "ControllerSettingsMotionControllerSlot": "ช่องเสียบ คอนโทรลเลอร์:", + "ControllerSettingsMotionMirrorInput": "นำเข้าการสะท้อน การควบคุม", + "ControllerSettingsMotionRightJoyConSlot": "ช่องเสียบ จอยคอน ด้านขวา:", + "ControllerSettingsMotionServerHost": "เซิร์ฟเวอร์โฮสต์:", + "ControllerSettingsMotionGyroSensitivity": "ความไวของไจโร:", + "ControllerSettingsMotionGyroDeadzone": "โซนที่ไม่ทำงานของไจโร:", + "ControllerSettingsSave": "บันทึก", + "ControllerSettingsClose": "ปิด", + "UserProfilesSelectedUserProfile": "โปรไฟล์ผู้ใช้งานที่เลือก:", + "UserProfilesSaveProfileName": "บันทึกชื่อโปรไฟล์", + "UserProfilesChangeProfileImage": "เปลี่ยนรูปโปรไฟล์", + "UserProfilesAvailableUserProfiles": "โปรไฟล์ผู้ใช้ที่ใช้งานได้:", + "UserProfilesAddNewProfile": "สร้างโปรไฟล์ใหม่", + "UserProfilesDelete": "ลบ", + "UserProfilesClose": "ปิด", + "ProfileNameSelectionWatermark": "เลือกชื่อเล่น", + "ProfileImageSelectionTitle": "เลือกรูปโปรไฟล์ของคุณ", + "ProfileImageSelectionHeader": "เลือกรูปโปรไฟล์", + "ProfileImageSelectionNote": "คุณสามารถนำเข้ารูปโปรไฟล์ที่กำหนดเอง หรือเลือกอวาต้าจากเฟิร์มแวร์ระบบได้", + "ProfileImageSelectionImportImage": "นำเข้าไฟล์รูปภาพ", + "ProfileImageSelectionSelectAvatar": "เลือกรูปอวาต้าเฟิร์มแวร์", + "InputDialogTitle": "กล่องโต้ตอบการป้อนข้อมูล", + "InputDialogOk": "ตกลง", + "InputDialogCancel": "ยกเลิก", + "InputDialogAddNewProfileTitle": "เลือกชื่อโปรไฟล์", + "InputDialogAddNewProfileHeader": "กรุณาใส่ชื่อโปรไฟล์", + "InputDialogAddNewProfileSubtext": "(ความยาวสูงสุด: {0})", + "AvatarChoose": "เลือกรูปอวาต้าของคุณ", + "AvatarSetBackgroundColor": "ตั้งค่าสีพื้นหลัง", + "AvatarClose": "ปิด", + "ControllerSettingsLoadProfileToolTip": "โหลดโปรไฟล์", + "ControllerSettingsAddProfileToolTip": "เพิ่มโปรไฟล์", + "ControllerSettingsRemoveProfileToolTip": "ลบโปรไฟล์", + "ControllerSettingsSaveProfileToolTip": "บันทึกโปรไฟล์", + "MenuBarFileToolsTakeScreenshot": "ถ่ายภาพหน้าจอ", + "MenuBarFileToolsHideUi": "ซ่อน UI", + "GameListContextMenuRunApplication": "เรียกใช้แอปพลิเคชัน", + "GameListContextMenuToggleFavorite": "สลับรายการโปรด", + "GameListContextMenuToggleFavoriteToolTip": "สลับสถานะเกมที่ชื่นชอบ", + "SettingsTabGeneralTheme": "ธีม:", + "SettingsTabGeneralThemeDark": "มืด", + "SettingsTabGeneralThemeLight": "สว่าง", + "ControllerSettingsConfigureGeneral": "กำหนดค่า", + "ControllerSettingsRumble": "การสั่นไหว", + "ControllerSettingsRumbleStrongMultiplier": "เพิ่มความแรงการสั่นไหว", + "ControllerSettingsRumbleWeakMultiplier": "ลดความแรงการสั่นไหว", + "DialogMessageSaveNotAvailableMessage": "ไม่มีข้อมูลบันทึกไว้สำหรับ {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "คุณต้องการสร้างข้อมูลบันทึกสำหรับเกมนี้หรือไม่?", + "DialogConfirmationTitle": "ริวจินซ์ - ยืนยัน", + "DialogUpdaterTitle": "รียูจินซ์ - อัพเดต", + "DialogErrorTitle": "รียูจินซ์ - ผิดพลาด", + "DialogWarningTitle": "รียูจินซ์ - คำเตือน", + "DialogExitTitle": "รียูจินซ์ - ออก", + "DialogErrorMessage": "รียูจินซ์ พบข้อผิดพลาด", + "DialogExitMessage": "คุณแน่ใจหรือไม่ว่าต้องการปิด ริวจินซ์ หรือไม่?", + "DialogExitSubMessage": "ข้อมูลที่ไม่ได้บันทึกทั้งหมดจะสูญหาย!", + "DialogMessageCreateSaveErrorMessage": "มีข้อผิดพลาดในการสร้างข้อมูลการบันทึกที่ระบุ: {0}", + "DialogMessageFindSaveErrorMessage": "มีข้อผิดพลาดในการค้นหาข้อมูลที่บันทึกไว้ที่ระบุ: {0}", + "FolderDialogExtractTitle": "เลือกโฟลเดอร์ที่จะแตกไฟล์เข้าไป", + "DialogNcaExtractionMessage": "กำลังแตกไฟล์ {0} จากส่วน {1}...", + "DialogNcaExtractionTitle": "รียูจินซ์ - เครื่องมือแตกไฟล์ของ NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "เกิดความล้มเหลวในการแตกไฟล์เนื่องจากไม่พบ NCA หลักในไฟล์ที่เลือก", + "DialogNcaExtractionCheckLogErrorMessage": "เกิดความล้มเหลวในการแตกไฟล์ โปรดอ่านไฟล์บันทึกเพื่อดูข้อมูลเพิ่มเติม", + "DialogNcaExtractionSuccessMessage": "การแตกไฟล์เสร็จสมบูรณ์แล้ว", + "DialogUpdaterConvertFailedMessage": "ไม่สามารถแปลงเวอร์ชั่น รียูจินซ์ ปัจจุบันได้", + "DialogUpdaterCancelUpdateMessage": "ยกเลิกการอัพเดต!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "คุณกำลังใช้ รียูจินซ์ เวอร์ชั่นที่อัปเดตล่าสุด!", + "DialogUpdaterFailedToGetVersionMessage": "เกิดข้อผิดพลาดขณะพยายามรับข้อมูลเวอร์ชั่นจาก GitHub Release ปัญหานี้อาจเกิดขึ้นได้หากมีการรวบรวมเวอร์ชั่นใหม่โดย GitHub Actions โปรดลองอีกครั้งในอีกไม่กี่นาทีข้างหน้า", + "DialogUpdaterConvertFailedGithubMessage": "ไม่สามารถแปลงเวอร์ชั่น รียูจินซ์ ที่ได้รับจาก Github Release", + "DialogUpdaterDownloadingMessage": "กำลังดาวน์โหลดอัปเดต...", + "DialogUpdaterExtractionMessage": "กำลังแตกไฟล์อัปเดต...", + "DialogUpdaterRenamingMessage": "กำลังลบไฟล์เก่า...", + "DialogUpdaterAddingFilesMessage": "กำลังเพิ่มไฟล์อัปเดตใหม่...", + "DialogUpdaterCompleteMessage": "อัปเดตเสร็จสมบูรณ์แล้ว!", + "DialogUpdaterRestartMessage": "คุณต้องการรีสตาร์ท รียูจินซ์ ตอนนี้หรือไม่?", + "DialogUpdaterNoInternetMessage": "คุณไม่ได้เชื่อมต่อกับอินเทอร์เน็ต!", + "DialogUpdaterNoInternetSubMessage": "โปรดตรวจสอบว่าคุณมีการเชื่อมต่ออินเทอร์เน็ตว่ามีการใช้งานได้หรือไม่!", + "DialogUpdaterDirtyBuildMessage": "คุณไม่สามารถอัปเดต Dirty build ของ รียูจินซ์ ได้!", + "DialogUpdaterDirtyBuildSubMessage": "โปรดดาวน์โหลด รียูจินซ์ ได้ที่ https://ryujinx.org/ หากคุณกำลังมองหาเวอร์ชั่นที่รองรับ", + "DialogRestartRequiredMessage": "จำเป็นต้องรีสตาร์ทเพื่อให้การอัพเดตสามารถให้งานได้", + "DialogThemeRestartMessage": "บันทึกธีมแล้ว จำเป็นต้องรีสตาร์ทเพื่อใช้ธีม", + "DialogThemeRestartSubMessage": "คุณต้องการรีสตาร์ทหรือไม่?", + "DialogFirmwareInstallEmbeddedMessage": "คุณต้องการติดตั้งเฟิร์มแวร์ที่ฝังอยู่ในเกมนี้หรือไม่? (เฟิร์มแวร์ {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "ไม่พบเฟิร์มแวร์ที่ติดตั้งไว้ แต่ รียูจินซ์ สามารถติดตั้งเฟิร์มแวร์ได้ {0} จากเกมที่ให้มา\\nตอนนี้โปรแกรมจำลองจะเริ่มทำงาน", + "DialogFirmwareNoFirmwareInstalledMessage": "ไม่มีการติดตั้งเฟิร์มแวร์", + "DialogFirmwareInstalledMessage": "เฟิร์มแวร์ทำการติดตั้งแล้ว {0}", + "DialogInstallFileTypesSuccessMessage": "ติดตั้งประเภทไฟล์สำเร็จแล้ว!", + "DialogInstallFileTypesErrorMessage": "ติดตั้งประเภทไฟล์ไม่สำเร็จ", + "DialogUninstallFileTypesSuccessMessage": "ถอนการติดตั้งประเภทไฟล์สำเร็จแล้ว!", + "DialogUninstallFileTypesErrorMessage": "ไม่สามารถถอนการติดตั้งประเภทไฟล์ได้", + "DialogOpenSettingsWindowLabel": "เปิดหน้าต่างการตั้งค่า", + "DialogControllerAppletTitle": "แอพเพล็ตคอนโทรลเลอร์", + "DialogMessageDialogErrorExceptionMessage": "เกิดข้อผิดพลาดในการแสดงกล่องโต้ตอบข้อความ: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "เกิดข้อผิดพลาดในการแสดงซอฟต์แวร์แป้นพิมพ์: {0}", + "DialogErrorAppletErrorExceptionMessage": "เกิดข้อผิดพลาดในการแสดงกล่องโต้ตอบ ข้อผิดพลาด แอปเพล็ต: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nสำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีแก้ไขข้อผิดพลาดนี้ โปรดทำตามคำแนะนำในการตั้งค่าของเรา", + "DialogUserErrorDialogTitle": "ข้อผิดพลาด รียูจินซ์ ({0})", + "DialogAmiiboApiTitle": "อะมิโบ API", + "DialogAmiiboApiFailFetchMessage": "เกิดข้อผิดพลาดขณะเรียกข้อมูลจาก API", + "DialogAmiiboApiConnectErrorMessage": "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ อะมิโบ API บ้างบริการอาจหยุดทำงาน หรือไม่คุณต้องทำการตรวจสอบว่าการเชื่อมต่ออินเทอร์เน็ตของคุณอยู่ในสถานะเชื่อมต่ออยู่หรือไม่", + "DialogProfileInvalidProfileErrorMessage": "โปรไฟล์ {0} เข้ากันไม่ได้กับระบบการกำหนดค่าอินพุตปัจจุบัน", + "DialogProfileDefaultProfileOverwriteErrorMessage": "ไม่สามารถเขียนทับโปรไฟล์เริ่มต้นได้", + "DialogProfileDeleteProfileTitle": "กำลังลบโปรไฟล์", + "DialogProfileDeleteProfileMessage": "การดำเนินการนี้ไม่สามารถย้อนกลับได้ คุณแน่ใจหรือไม่ว่าต้องการดำเนินการต่อหรือไม่?", + "DialogWarning": "คำเตือน", + "DialogPPTCDeletionMessage": "คุณกำลังจะจัดคิวการสร้าง PPTC ใหม่ในการบูตครั้งถัดไป:\n\n{0}\n\nคุณแน่ใจหรือไม่ว่าต้องการดำเนินการต่อหรือไม่?", + "DialogPPTCDeletionErrorMessage": "มีข้อผิดพลาดในการล้างแคช PPTC {0}: {1}", + "DialogShaderDeletionMessage": "คุณกำลังจะลบ เชเดอร์แคช:\n\n{0}\n\nคุณแน่ใจหรือไม่ว่าต้องการดำเนินการต่อหรือไม่?", + "DialogShaderDeletionErrorMessage": "เกิดข้อผิดพลาดในการล้าง เชเดอร์แคช {0}: {1}", + "DialogRyujinxErrorMessage": "รียูจินซ์ พบข้อผิดพลาด", + "DialogInvalidTitleIdErrorMessage": "ข้อผิดพลาดของ ยูไอ: เกมที่เลือกไม่มีชื่อ ID ที่ถูกต้อง", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "ไม่พบเฟิร์มแวร์ของระบบที่ถูกต้อง {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "ติดตั้งเฟิร์มแวร์ {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "นี่คื่อเวอร์ชั่นของระบบ {0} ที่ได้รับการติดตั้งเมื่อเร็วๆ นี้", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nสิ่งนี้จะแทนที่เวอร์ชั่นของระบบปัจจุบัน {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nคุณต้องการดำเนินการต่อหรือไม่?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "กำลังติดตั้งเฟิร์มแวร์...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "การติดตั้งเวอร์ชั่นระบบ {0} เรียบร้อยแล้ว", + "DialogUserProfileDeletionWarningMessage": "จะไม่มีโปรไฟล์อื่นให้เปิดหากโปรไฟล์ที่เลือกถูกลบ", + "DialogUserProfileDeletionConfirmMessage": "คุณต้องการลบโปรไฟล์ที่เลือกหรือไม่?", + "DialogUserProfileUnsavedChangesTitle": "คำเตือน - มีการเปลี่ยนแปลงที่ไม่ได้บันทึก", + "DialogUserProfileUnsavedChangesMessage": "คุณได้ทำการเปลี่ยนแปลงโปรไฟล์ผู้ใช้นี้โดยไม่ได้รับการบันทึก", + "DialogUserProfileUnsavedChangesSubMessage": "คุณต้องการยกเลิกการเปลี่ยนแปลงของคุณหรือไม่?", + "DialogControllerSettingsModifiedConfirmMessage": "การตั้งค่าคอนโทรลเลอร์ปัจจุบันได้รับการอัปเดตแล้ว", + "DialogControllerSettingsModifiedConfirmSubMessage": "คุณต้องการบันทึกหรือไม่?", + "DialogLoadFileErrorMessage": "{0} ไฟล์เกิดข้อผิดพลาด: {1}", + "DialogModAlreadyExistsMessage": "มีม็อดอยู่แล้ว", + "DialogModInvalidMessage": "ไดเร็กทอรีที่ระบุไม่มี ม็อดอยู่!", + "DialogModDeleteNoParentMessage": "ไม่สามารถลบ: ไม่พบไดเร็กทอรีหลักสำหรับ ม็อด \"{0}\"!", + "DialogDlcNoDlcErrorMessage": "ไฟล์ที่ระบุไม่มี DLC สำหรับชื่อที่เลือก!", + "DialogPerformanceCheckLoggingEnabledMessage": "คุณได้เปิดใช้งานการบันทึกการติดตาม ซึ่งออกแบบมาเพื่อให้นักพัฒนาใช้เท่านั้น", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "เพื่อประสิทธิภาพสูงสุด ขอแนะนำให้ปิดใช้งานการบันทึกการติดตาม คุณต้องการปิดใช้การบันทึกการติดตามตอนนี้หรือไม่?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "คุณได้เปิดใช้งาน การดัมพ์เชเดอร์ ซึ่งออกแบบมาเพื่อให้นักพัฒนาใช้งานเท่านั้น", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "เพื่อประสิทธิภาพสูงสุด ขอแนะนำให้ปิดใช้การดัมพ์เชเดอร์ คุณต้องการปิดการใช้งานการ ดัมพ์เชเดอร์ ตอนนี้หรือไม่?", + "DialogLoadAppGameAlreadyLoadedMessage": "ทำการโหลดเกมเรียบร้อยแล้ว", + "DialogLoadAppGameAlreadyLoadedSubMessage": "โปรดหยุดการจำลอง หรือปิดโปรแกรมจำลองก่อนที่จะเปิดเกมอื่น", + "DialogUpdateAddUpdateErrorMessage": "ไฟล์ที่ระบุไม่มีการอัพเดตสำหรับชื่อเรื่องที่เลือก!", + "DialogSettingsBackendThreadingWarningTitle": "คำเตือน - การทำเธรดแบ็กเอนด์", + "DialogSettingsBackendThreadingWarningMessage": "รียูจินซ์ ต้องรีสตาร์ทหลังจากเปลี่ยนตัวเลือกนี้จึงจะใช้งานได้อย่างสมบูรณ์ คุณอาจต้องปิดการใช้งาน มัลติเธรด ของไดรเวอร์ของคุณด้วยตนเองเมื่อใช้ รียูจินซ์ ทั้งนี้ขึ้นอยู่กับแพลตฟอร์มของคุณ", + "DialogModManagerDeletionWarningMessage": "คุณกำลังจะลบ ม็อด: {0}\n\nคุณแน่ใจหรือไม่ว่าต้องการดำเนินการต่อ?", + "DialogModManagerDeletionAllWarningMessage": "คุณกำลังจะลบม็อดทั้งหมดสำหรับชื่อนี้\n\nคุณแน่ใจหรือไม่ว่าต้องการดำเนินการต่อ?", + "SettingsTabGraphicsFeaturesOptions": "คุณสมบัติ", + "SettingsTabGraphicsBackendMultithreading": "มัลติเธรด แบ็กเอนด์กราฟิก:", + "CommonAuto": "อัตโนมัติ", + "CommonOff": "ปิดการใช้งาน", + "CommonOn": "เปิดใช้งาน", + "InputDialogYes": "ใช่", + "InputDialogNo": "ไม่ใช่", + "DialogProfileInvalidProfileNameErrorMessage": "ชื่อไฟล์ประกอบด้วยอักขระที่ไม่ถูกต้อง กรุณาลองอีกครั้ง", + "MenuBarOptionsPauseEmulation": "หยุดชั่วคราว", + "MenuBarOptionsResumeEmulation": "ดำเนินการต่อ", + "AboutUrlTooltipMessage": "คลิกเพื่อเปิดเว็บไซต์ รียูจินซ์ บนเบราว์เซอร์เริ่มต้นของคุณ", + "AboutDisclaimerMessage": "ทางผู้พัฒนาโปรแกรม รียูจินซ์ ไม่มีส่วนเกี่ยวข้องกับทางบริษัท Nintendo™\nหรือพันธมิตรใดๆ ทั้งสิ้น!", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) ถูกใช้\nในการจำลอง อะมิโบ ของเรา", + "AboutPatreonUrlTooltipMessage": "คลิกเพื่อเปิดหน้า เพทรีออน ของ รียูจินซ์ บนเบราว์เซอร์เริ่มต้นของคุณ", + "AboutGithubUrlTooltipMessage": "คลิกเพื่อเปิดหน้า กิตฮับ ของ ริวจินซ์ บนเบราว์เซอร์เริ่มต้นของคุณ", + "AboutDiscordUrlTooltipMessage": "คลิกเพื่อเปิดคำเชิญเข้าสู่เซิร์ฟเวอร์ ดิสคอร์ด ของ รียูจินซ์ บนเบราว์เซอร์เริ่มต้นของคุณ", + "AboutTwitterUrlTooltipMessage": "คลิกเพื่อเปิดหน้าเพจ ทวิตเตอร์ ของ รียูจินซ์ บนเบราว์เซอร์เริ่มต้นของคุณ", + "AboutRyujinxAboutTitle": "เกี่ยวกับ:", + "AboutRyujinxAboutContent": "รียูจินซ์ เป็นอีมูเลเตอร์สำหรับ Nintendo Switch™\nโปรดสนับสนุนเราบน เพทรีออน\nรับข่าวสารล่าสุดทั้งหมดบน ทวิตเตอร์ หรือ ดิสคอร์ด ของเรา\nนักพัฒนาที่สนใจจะมีส่วนร่วมสามารถดูข้อมูลเพิ่มเติมได้ที่ กิตฮับ หรือ ดิสคอร์ด ของเรา", + "AboutRyujinxMaintainersTitle": "ได้รับการดูแลรักษาโดย:", + "AboutRyujinxMaintainersContentTooltipMessage": "คลิกเพื่อเปิดหน้าผู้ร่วมให้ข้อมูลในเบราว์เซอร์เริ่มต้นของคุณ", + "AboutRyujinxSupprtersTitle": "สนับสนุนบน เพทรีออน โดย:", + "AmiiboSeriesLabel": "อะมิโบซีรีส์", + "AmiiboCharacterLabel": "ตัวละคร", + "AmiiboScanButtonLabel": "สแกนเลย", + "AmiiboOptionsShowAllLabel": "แสดง อะมิโบ ทั้งหมด", + "AmiiboOptionsUsRandomTagLabel": "แฮ็ค: ใช้แท็กสุ่ม Uuid", + "DlcManagerTableHeadingEnabledLabel": "เปิดใช้งานแล้ว", + "DlcManagerTableHeadingTitleIdLabel": "ชื่อไอดี", + "DlcManagerTableHeadingContainerPathLabel": "ที่เก็บไฟล์ คอนเทนเนอร์", + "DlcManagerTableHeadingFullPathLabel": "ที่เก็บไฟล์แบบเต็ม", + "DlcManagerRemoveAllButton": "ลบทั้งหมด", + "DlcManagerEnableAllButton": "เปิดใช้งานทั้งหมด", + "DlcManagerDisableAllButton": "ปิดใช้งานทั้งหมด", + "ModManagerDeleteAllButton": "ลบทั้งหมด", + "MenuBarOptionsChangeLanguage": "เปลี่ยนภาษา", + "MenuBarShowFileTypes": "แสดงประเภทของไฟล์", + "CommonSort": "เรียงลำดับ", + "CommonShowNames": "แสดงชื่อ", + "CommonFavorite": "สิ่งที่ชื่นชอบ", + "OrderAscending": "จากน้อยไปมาก", + "OrderDescending": "จากมากไปน้อย", + "SettingsTabGraphicsFeatures": "คุณสมบัติ และ การเพิ่มประสิทธิภาพ", + "ErrorWindowTitle": "หน้าต่างแสดงข้อผิดพลาด", + "ToggleDiscordTooltip": "เลือกว่าจะแสดง รียูจินซ์ ในกิจกรรม ดิสคอร์ด \"ที่กำลังเล่นอยู่\" ของคุณหรือไม่?", + "AddGameDirBoxTooltip": "ป้อนไดเรกทอรี่เกมที่จะทำการเพิ่มลงในรายการ", + "AddGameDirTooltip": "เพิ่มไดเรกทอรี่เกมลงในรายการ", + "RemoveGameDirTooltip": "ลบไดเรกทอรี่เกมที่เลือก", + "CustomThemeCheckTooltip": "ใช้ธีม Avalonia แบบกำหนดเองสำหรับ GUI เพื่อเปลี่ยนรูปลักษณ์ของเมนูโปรแกรมจำลอง", + "CustomThemePathTooltip": "ไปยังที่เก็บไฟล์ธีม GUI แบบกำหนดเอง", + "CustomThemeBrowseTooltip": "เรียกดูธีม GUI ที่กำหนดเอง", + "DockModeToggleTooltip": "ด็อกโหมดทำให้ระบบจำลองการทำงานเสมือน Nintendo ที่กำลังเชื่อมต่ออยู่ด็อก สิ่งนี้จะปรับปรุงความเสถียรภาพของกราฟิกในเกมส่วนใหญ่ ในทางกลับกัน การปิดใช้จะทำให้ระบบจำลองทำงานเหมือนกับ Nintendo Switch แบบพกพา ส่งผลให้คุณภาพกราฟิกลดลง\n\nกำหนดค่าส่วนควบคุมของผู้เล่น 1 หากวางแผนที่จะใช้ด็อกโหมด กำหนดค่าการควบคุมแบบ แฮนด์เฮลด์ หากวางแผนที่จะใช้โหมดแฮนด์เฮลด์\n\nเปิดทิ้งไว้หากคุณไม่แน่ใจ", + "DirectKeyboardTooltip": "รองรับการเข้าถึงแป้นพิมพ์โดยตรง (HID) ให้เกมเข้าถึงคีย์บอร์ดของคุณเป็นอุปกรณ์ป้อนข้อความ\n\nใช้งานได้กับเกมที่รองรับการใช้งานคีย์บอร์ดบนฮาร์ดแวร์ของ Switch เท่านั้น\n\nหากคุณไม่แน่ใจปล่อยให้ปิดอย่างนั้น", + "DirectMouseTooltip": "รองรับการเข้าถึงเมาส์โดยตรง (HID) ให้เกมเข้าถึงเมาส์ของคุณเป็นอุปกรณ์ชี้ตำแหน่ง\n\nใช้งานได้เฉพาะกับเกมที่รองรับการควบคุมเมาส์บนฮาร์ดแวร์ของ Switch เท่านั้น ซึ่งมีอยู่ไม่มากนัก\n\nเมื่อเปิดใช้งาน ฟังก์ชั่นหน้าจอสัมผัสอาจไม่ทำงาน\n\nหากคุณไม่แน่ใจปล่อยให้ปิดอย่างนั้น", + "RegionTooltip": "เปลี่ยนภูมิภาคของระบบ", + "LanguageTooltip": "เปลี่ยนภาษาของระบบ", + "TimezoneTooltip": "เปลี่ยนโซนเวลาของระบบ", + "TimeTooltip": "เปลี่ยนเวลาของระบบ", + "VSyncToggleTooltip": "Vertical Sync ของคอนโซลจำลอง โดยพื้นฐานแล้วเป็นตัวจำกัดเฟรมสำหรับเกมส่วนใหญ่ การปิดใช้งานอาจทำให้เกมทำงานด้วยความเร็วสูงขึ้น หรือทำให้หน้าจอการโหลดใช้เวลานานขึ้นหรือค้าง\n\nสามารถสลับได้ในเกมด้วยปุ่มลัดตามที่คุณต้องการ (F1 เป็นค่าเริ่มต้น) เราขอแนะนำให้ทำเช่นนี้หากคุณวางแผนที่จะปิดการใช้งาน\n\nหากคุณไม่แน่ใจให้ปล่อยไว้อย่างนั้น", + "PptcToggleTooltip": "บันทึกฟังก์ชั่น JIT ที่แปลแล้ว ดังนั้นจึงไม่จำเป็นต้องแปลทุกครั้งที่โหลดเกม\n\nลดอาการกระตุกและเร่งความเร็วการบูตได้อย่างมากหลังจากการบูตครั้งแรกของเกม\n\nปล่อยไว้หากคุณไม่แน่ใจ", + "FsIntegrityToggleTooltip": "ตรวจสอบไฟล์ที่เสียหายเมื่อบูตเกม และหากตรวจพบไฟล์ที่เสียหาย จะแสดงข้อผิดพลาดของแฮชในบันทึก\n\nไม่มีผลกระทบต่อประสิทธิภาพการทำงานและมีไว้เพื่อช่วยในการแก้ไขปัญหา\n\nปล่อยไว้หากคุณไม่แน่ใจ", + "AudioBackendTooltip": "เปลี่ยนแบ็กเอนด์ที่ใช้ในการเรนเดอร์เสียง\n\nSDL2 เป็นที่ต้องการ ในขณะที่ OpenAL และ SoundIO ถูกใช้เป็นทางเลือกสำรอง ดัมมี่จะไม่มีเสียง\n\nปล่อยไว้หากคุณไม่แน่ใจ", + "MemoryManagerTooltip": "เปลี่ยนวิธีการแมปและเข้าถึงหน่วยความจำของผู้เยี่ยมชม ส่งผลอย่างมากต่อประสิทธิภาพการทำงานของ CPU ที่จำลอง\n\nตั้งค่าเป็น ไม่ทำการตรวจสอบ โฮสต์ หากคุณไม่แน่ใจ", + "MemoryManagerSoftwareTooltip": "ใช้ตารางหน้าซอฟต์แวร์สำหรับการแปลที่อยู่ ความแม่นยำสูงสุดแต่ประสิทธิภาพช้าที่สุด", + "MemoryManagerHostTooltip": "แมปหน่วยความจำในพื้นที่ที่อยู่โฮสต์โดยตรง การคอมไพล์และดำเนินการ JIT เร็วขึ้นมาก", + "MemoryManagerUnsafeTooltip": "แมปหน่วยความจำโดยตรง แต่อย่าปิดบังที่อยู่ภายในพื้นที่ที่อยู่ของผู้เยี่ยมชมก่อนที่จะเข้าถึง เร็วกว่า แต่ต้องแลกกับความปลอดภัย แอปพลิเคชั่นผู้เยี่ยมชมสามารถเข้าถึงหน่วยความจำได้จากทุกที่ใน รียูจินซ์ ดังนั้นให้รันเฉพาะโปรแกรมที่คุณเชื่อถือในโหมดนี้", + "UseHypervisorTooltip": "ใช้ Hypervisor แทน JIT ปรับปรุงประสิทธิภาพอย่างมากเมื่อพร้อมใช้งาน แต่อาจไม่เสถียรในสถานะปัจจุบัน", + "DRamTooltip": "ใช้เค้าโครง MemoryMode ทางเลือกเพื่อเลียนแบบโมเดลการพัฒนาสวิตช์\n\nสิ่งนี้มีประโยชน์สำหรับแพ็กพื้นผิวที่มีความละเอียดสูงกว่าหรือม็อดที่มีความละเอียด 4k เท่านั้น ไม่ปรับปรุงประสิทธิภาพ\n\nปล่อยให้ปิดหากคุณไม่แน่ใจ", + "IgnoreMissingServicesTooltip": "ละเว้นบริการ Horizon OS ที่ยังไม่ได้ใช้งาน วิธีนี้อาจช่วยในการหลีกเลี่ยงข้อผิดพลาดเมื่อบู๊ตเกมบางเกม\n\nปล่อยให้ปิดหากคุณไม่แน่ใจ", + "GraphicsBackendThreadingTooltip": "ดำเนินการคำสั่งแบ็กเอนด์กราฟิกบนเธรดที่สอง\n\nเร่งความเร็วการคอมไพล์เชเดอร์ ลดการกระตุก และปรับปรุงประสิทธิภาพการทำงานของไดรเวอร์ GPU โดยไม่ต้องรองรับมัลติเธรดในตัว ประสิทธิภาพที่ดีขึ้นเล็กน้อยสำหรับไดรเวอร์ที่มีมัลติเธรด\n\nตั้งเป็น อัตโนมัติ หากคุณไม่แน่ใจ", + "GalThreadingTooltip": "ดำเนินการคำสั่งแบ็กเอนด์กราฟิกบนเธรดที่สอง\n\nเร่งความเร็วการคอมไพล์เชเดอร์ ลดการกระตุก และปรับปรุงประสิทธิภาพการทำงานของไดรเวอร์ GPU โดยไม่ต้องรองรับมัลติเธรดในตัว ประสิทธิภาพที่ดีขึ้นเล็กน้อยสำหรับไดรเวอร์ที่มีมัลติเธรด\n\nตั้งเป็น อัตโนมัติ หากคุณไม่แน่ใจ", + "ShaderCacheToggleTooltip": "บันทึกแคชเชเดอร์ของดิสก์ซึ่งช่วยลดการกระตุกในการรันครั้งต่อๆ ไป\n\nปล่อยไว้หากคุณไม่แน่ใจ", + "ResolutionScaleTooltip": "คูณความละเอียดการเรนเดอร์ของเกม\n\nเกมบางเกมอาจไม่สามารถใช้งานได้และดูเป็นพิกเซลแม้ว่าความละเอียดจะเพิ่มขึ้นก็ตาม สำหรับเกมเหล่านั้น คุณอาจต้องค้นหาม็อดที่ลบรอยหยักของภาพหรือเพิ่มความละเอียดในการเรนเดอร์ภายใน หากต้องการใช้อย่างหลัง คุณอาจต้องเลือก Native\n\nตัวเลือกนี้สามารถเปลี่ยนแปลงได้ในขณะที่เกมกำลังทำงานอยู่โดยคลิก \"นำมาใช้\" ด้านล่าง คุณสามารถย้ายหน้าต่างการตั้งค่าไปด้านข้างและทดลองจนกว่าคุณจะพบรูปลักษณ์ที่คุณต้องการสำหรับเกม\n\nโปรดทราบว่า 4x นั้นเกินความจำเป็นสำหรับการตั้งค่าแทบทุกประเภท", + "ResolutionScaleEntryTooltip": "สเกลความละเอียดจุดทศนิยม เช่น 1.5 ไม่ใช่จำนวนเต็มของสเกล มีแนวโน้มที่จะก่อให้เกิดปัญหาหรือความผิดพลาดได้", + "AnisotropyTooltip": "ระดับของการกรองแบบ Anisotropic ตั้งค่าเป็นอัตโนมัติเพื่อใช้ค่าที่เกมร้องขอ", + "AspectRatioTooltip": "อัตราส่วนภาพที่ใช้กับหน้าต่างตัวแสดงภาพ\n\nเปลี่ยนสิ่งนี้หากคุณใช้ตัวดัดแปลงอัตราส่วนกว้างยาวสำหรับเกมของคุณ ไม่เช่นนั้นกราฟิกจะถูกยืดออก\n\nทิ้งไว้ที่ 16:9 หากไม่แน่ใจ", + "ShaderDumpPathTooltip": "ที่เก็บ ดัมพ์ไฟล์ของ เชเดอร์กราฟิก", + "FileLogTooltip": "บันทึกการบันทึกคอนโซลลงในไฟล์บันทึกบนดิสก์ จะไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", + "StubLogTooltip": "พิมพ์ข้อความบันทึกต้นขั้วในคอนโซล จะไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", + "InfoLogTooltip": "พิมพ์ข้อความบันทึกข้อมูลในคอนโซล จะไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", + "WarnLogTooltip": "พิมพ์ข้อความบันทึกแจ้งตือนในคอนโซล จะไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", + "ErrorLogTooltip": "พิมพ์ข้อความบันทึกข้อผิดพลาดในคอนโซล จะไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", + "TraceLogTooltip": "พิมพ์ข้อความบันทึกการติดตามในคอนโซล ไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", + "GuestLogTooltip": "พิมพ์ข้อความบันทึกของผู้เยี่ยมชมในคอนโซล ไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", + "FileAccessLogTooltip": "พิมพ์ข้อความบันทึกการเข้าถึงไฟล์ในคอนโซล", + "FSAccessLogModeTooltip": "เปิดใช้งานเอาต์พุตบันทึกการเข้าถึง FS ไปยังคอนโซล โหมดที่เป็นไปได้คือ 0-3", + "DeveloperOptionTooltip": "โปรดใช้ด้วยความระมัดระวัง", + "OpenGlLogLevel": "จำเป็นต้องเปิดใช้งานระดับบันทึกที่เหมาะสม", + "DebugLogTooltip": "พิมพ์ข้อความบันทึกการแก้ไขข้อบกพร่องในคอนโซล\n\nใช้สิ่งนี้เฉพาะเมื่อได้รับคำแนะนำจากเจ้าหน้าที่โดยเฉพาะเท่านั้น เนื่องจากจะทำให้บันทึกอ่านยากและทำให้ประสิทธิภาพของโปรแกรมจำลองแย่ลง", + "LoadApplicationFileTooltip": "เปิด File Explorer เพื่อเลือกไฟล์ที่เข้ากันได้กับ Switch ที่จะโหลด", + "LoadApplicationFolderTooltip": "เปิดตัวสำรวจไฟล์เพื่อเลือกไฟล์ที่เข้ากันได้กับ Switch ที่จะโหลด", + "OpenRyujinxFolderTooltip": "เปิดโฟลเดอร์ระบบไฟล์ รียูจินซ์", + "OpenRyujinxLogsTooltip": "เปิดโฟลเดอร์ที่มีการเขียนบันทึก", + "ExitTooltip": "ออกจากโปรแกรม รียูจินซ์", + "OpenSettingsTooltip": "เปิดหน้าต่างการตั้งค่า", + "OpenProfileManagerTooltip": "เปิดหน้าต่างตัวจัดการโปรไฟล์ผู้ใช้", + "StopEmulationTooltip": "หยุดการจำลองเกมปัจจุบันและกลับไปยังการเลือกเกม", + "CheckUpdatesTooltip": "ตรวจสอบการอัปเดตของ รียูจินซ์", + "OpenAboutTooltip": "เปิดหน้าต่าง เกี่ยวกับ", + "GridSize": "ขนาดตาราง", + "GridSizeTooltip": "เปลี่ยนขนาด ของตาราง", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "บราซิล โปรตุเกส", + "AboutRyujinxContributorsButtonHeader": "ดูผู้มีส่วนร่วมทั้งหมด", + "SettingsTabSystemAudioVolume": "ระดับเสียง: ", + "AudioVolumeTooltip": "ปรับระดับเสียง", + "SettingsTabSystemEnableInternetAccess": "การเข้าถึงอินเทอร์เน็ตของผู้เยี่ยมชม/โหมด LAN", + "EnableInternetAccessTooltip": "อนุญาตให้แอปพลิเคชันจำลองเชื่อมต่ออินเทอร์เน็ต\n\nเกมที่มีโหมด LAN สามารถเชื่อมต่อระหว่างกันได้เมื่อเปิดใช้งานและระบบเชื่อมต่อกับจุดเชื่อมต่อเดียวกัน รวมถึงคอนโซลจริงด้วย\n\nไม่อนุญาตให้มีการเชื่อมต่อกับเซิร์ฟเวอร์ Nintendo อาจทำให้เกิดการหยุดทำงานในบางเกมที่พยายามเชื่อมต่ออินเทอร์เน็ต\n\nปล่อยให้ปิดหากคุณไม่แน่ใจ", + "GameListContextMenuManageCheatToolTip": "ฟังชั่นจัดการสูตรโกง", + "GameListContextMenuManageCheat": "จัดการสูตรโกง", + "GameListContextMenuManageModToolTip": "จัดการ ม็อด", + "GameListContextMenuManageMod": "จัดการ ม็อด", + "ControllerSettingsStickRange": "ขอบเขต:", + "DialogStopEmulationTitle": "รียูจินซ์ - หยุดการจำลอง", + "DialogStopEmulationMessage": "คุณแน่ใจหรือไม่ว่าต้องการหยุดการจำลองหรือไม่?", + "SettingsTabCpu": "หน่วยประมวลผลกลาง", + "SettingsTabAudio": "เสียง", + "SettingsTabNetwork": "เครือข่าย", + "SettingsTabNetworkConnection": "การเชื่อมต่อเครือข่าย", + "SettingsTabCpuCache": "ซีพียู แคช", + "SettingsTabCpuMemory": "ซีพียูเมมโมรี่ แคช", + "DialogUpdaterFlatpakNotSupportedMessage": "โปรดอัปเดต รียูจินซ์ ผ่านช่องทาง FlatHub", + "UpdaterDisabledWarningTitle": "ปิดใช้งานการอัปเดตแล้ว!", + "ControllerSettingsRotate90": "หมุน 90 องศา ตามเข็มนาฬิกา", + "IconSize": "ขนาดไอคอน", + "IconSizeTooltip": "เปลี่ยนขนาดของไอคอนเกม", + "MenuBarOptionsShowConsole": "แสดง คอนโซล", + "ShaderCachePurgeError": "เกิดข้อผิดพลาดในการล้างแคชเชเดอร์ {0}: {1}", + "UserErrorNoKeys": "ไม่พบคีย์", + "UserErrorNoFirmware": "ไม่พบเฟิร์มแวร์", + "UserErrorFirmwareParsingFailed": "เกิดข้อผิดพลาดในการแยกวิเคราะห์เฟิร์มแวร์", + "UserErrorApplicationNotFound": "ไม่พบแอปพลิเคชัน", + "UserErrorUnknown": "ข้อผิดพลาดที่ไม่รู้จัก", + "UserErrorUndefined": "ข้อผิดพลาดที่ไม่ได้ระบุ", + "UserErrorNoKeysDescription": "รียูจินซ์ ไม่พบไฟล์ 'prod.keys' ของคุณ", + "UserErrorNoFirmwareDescription": "รียูจินซ์ ไม่พบเฟิร์มแวร์ที่ติดตั้งไว้", + "UserErrorFirmwareParsingFailedDescription": "รียูจินซ์ ไม่สามารถแยกวิเคราะห์เฟิร์มแวร์ที่ให้มาได้ ซึ่งมักมีสาเหตุมาจากคีย์ที่ล้าสมัย", + "UserErrorApplicationNotFoundDescription": "รียูจินซ์ ไม่พบแอปพลิเคชันที่ถูกต้องในที่เก็บไฟล์ที่กำหนด", + "UserErrorUnknownDescription": "เกิดข้อผิดพลาดที่ไม่รู้จักเกิดขึ้น!", + "UserErrorUndefinedDescription": "เกิดข้อผิดพลาดที่ไม่สามารถระบุได้! สิ่งนี้ไม่ควรเกิดขึ้น โปรดติดต่อผู้พัฒนา!", + "OpenSetupGuideMessage": "เปิดคู่มือการตั้งค่า", + "NoUpdate": "ไม่มีการอัพเดต", + "TitleUpdateVersionLabel": "เวอร์ชั่น {0}", + "RyujinxInfo": "รียูจินซ์ – ข้อมูล", + "RyujinxConfirm": "รียูจินซ์ - ยืนยัน", + "FileDialogAllTypes": "ทุกประเภท", + "Never": "ไม่มี", + "SwkbdMinCharacters": "ต้องมีความยาวของตัวอักษรอย่างน้อย {0} ตัว", + "SwkbdMinRangeCharacters": "ต้องมีความยาวของตัวอักษร {0}-{1} ตัว", + "SoftwareKeyboard": "ซอฟต์แวร์ ของคีย์บอร์ด", + "SoftwareKeyboardModeNumeric": "ต้องเป็น 0-9 หรือ '.' เท่านั้น", + "SoftwareKeyboardModeAlphabet": "ต้องเป็นตัวอักษรที่ไม่ใช่ CJK เท่านั้น", + "SoftwareKeyboardModeASCII": "ต้องเป็นตัวอักษร ASCII เท่านั้น", + "ControllerAppletControllers": "คอนโทรลเลอร์ที่รองรับ:", + "ControllerAppletPlayers": "ผู้เล่น:", + "ControllerAppletDescription": "การกำหนดค่าปัจจุบันของคุณไม่ถูกต้อง เปิดการตั้งค่าและกำหนดค่าอินพุตของคุณใหม่", + "ControllerAppletDocked": "ตั้งค่าด็อกโหมด ควรปิดใช้งานการควบคุมแบบแฮนด์เฮลด์", + "UpdaterRenaming": "กำลังเปลี่ยนชื่อไฟล์เก่า...", + "UpdaterRenameFailed": "โปรแกรมอัปเดตไม่สามารถเปลี่ยนชื่อไฟล์ได้: {0}", + "UpdaterAddingFiles": "กำลังเพิ่มไฟล์ใหม่...", + "UpdaterExtracting": "กำลังแยกการอัปเดต...", + "UpdaterDownloading": "กำลังดาวน์โหลดอัปเดต...", + "Game": "เกมส์", + "Docked": "ด็อก", + "Handheld": "แฮนด์เฮลด์", + "ConnectionError": "การเชื่อมต่อล้มเหลว", + "AboutPageDeveloperListMore": "{0} และอื่น ๆ...", + "ApiError": "ข้อผิดพลาดของ API", + "LoadingHeading": "กำลังโหลด {0}", + "CompilingPPTC": "กำลังคอมไพล์ PTC", + "CompilingShaders": "กำลังคอมไพล์ เชเดอร์", + "AllKeyboards": "คีย์บอร์ดทั้งหมด", + "OpenFileDialogTitle": "เลือกไฟล์ที่สนับสนุนเพื่อเปิด", + "OpenFolderDialogTitle": "เลือกโฟลเดอร์ที่มีเกมที่แตกไฟล์แล้ว", + "AllSupportedFormats": "รูปแบบที่รองรับทั้งหมด", + "RyujinxUpdater": "อัพเดต รียูจินซ์", + "SettingsTabHotkeys": "ปุ่มลัดของคีย์บอร์ด", + "SettingsTabHotkeysHotkeys": "ปุ่มลัดของคีย์บอร์ด", + "SettingsTabHotkeysToggleVsyncHotkey": "สลับเป็น VSync:", + "SettingsTabHotkeysScreenshotHotkey": "ภาพหน้าจอ:", + "SettingsTabHotkeysShowUiHotkey": "แสดง UI:", + "SettingsTabHotkeysPauseHotkey": "หยุดชั่วคราว:", + "SettingsTabHotkeysToggleMuteHotkey": "ปิดเสียง:", + "ControllerMotionTitle": "การตั้งค่าการควบคุมการเคลื่อนไหว", + "ControllerRumbleTitle": "ตั้งค่าการสั่นไหว", + "SettingsSelectThemeFileDialogTitle": "เลือกไฟล์ธีม", + "SettingsXamlThemeFile": "ไฟล์ธีมรูปแบบ XAML", + "AvatarWindowTitle": "จัดการบัญชี - อวาต้า", + "Amiibo": "อะมิโบ", + "Unknown": "ไม่รู้จัก", + "Usage": "การใช้งาน", + "Writable": "สามารถเขียนได้", + "SelectDlcDialogTitle": "เลือกไฟล์ DLC", + "SelectUpdateDialogTitle": "เลือกไฟล์อัพเดต", + "SelectModDialogTitle": "เลือกไดเรกทอรี ม็อด", + "UserProfileWindowTitle": "จัดการโปรไฟล์ผู้ใช้", + "CheatWindowTitle": "จัดการสูตรโกง", + "DlcWindowTitle": "จัดการเนื้อหาที่ดาวน์โหลดได้สำหรับ {0} ({1})", + "UpdateWindowTitle": "จัดการการอัพเดตชื่อเรื่อง", + "CheatWindowHeading": "สูตรโกงมีให้สำหรับ {0} [{1}]", + "BuildId": "รหัสบิวด์:", + "DlcWindowHeading": "{0} เนื้อหาที่สามารถดาวน์โหลดได้", + "ModWindowHeading": "{0} ม็อด", + "UserProfilesEditProfile": "แก้ไขที่เลือกแล้ว", + "Cancel": "ยกเลิก", + "Save": "บันทึก", + "Discard": "ละทิ้ง", + "Paused": "หยุดชั่วคราว", + "UserProfilesSetProfileImage": "ตั้งค่ารูปโปรไฟล์", + "UserProfileEmptyNameError": "จำเป็นต้องมีการระบุชื่อ", + "UserProfileNoImageError": "จำเป็นต้องตั้งค่ารูปโปรไฟล์", + "GameUpdateWindowHeading": "จัดการอัพเดตสำหรับ {0} ({1})", + "SettingsTabHotkeysResScaleUpHotkey": "เพิ่มความละเอียด:", + "SettingsTabHotkeysResScaleDownHotkey": "ลดความละเอียด:", + "UserProfilesName": "ชื่อ:", + "UserProfilesUserId": "รหัสผู้ใช้:", + "SettingsTabGraphicsBackend": "แบ็กเอนด์กราฟิก", + "SettingsTabGraphicsBackendTooltip": "เลือกแบ็กเอนด์กราฟิกที่จะใช้ในโปรแกรมจำลอง\n\nโดยรวมแล้ว Vulkan นั้นดีกว่าสำหรับกราฟิกการ์ดรุ่นใหม่ทั้งหมด ตราบใดที่ไดรเวอร์ยังอัพเดทอยู่เสมอ Vulkan ยังมีคุณสมบัติการคอมไพล์เชเดอร์ที่เร็วขึ้น (ลดอาการกระตุก) ของผู้จำหน่าย GPU ทุกรายอยู่แล้ว\n\nOpenGL อาจได้รับผลลัพธ์ที่ดีกว่าบน Nvidia GPU รุ่นเก่า, AMD GPU รุ่นเก่าบน Linux หรือบน GPU ที่มี VRAM ต่ำกว่า แม้ว่าการคอมไพล์เชเดอร์จะสะดุดมากขึ้นก็ตาม\n\nตั้งค่าเป็น Vulkan หากไม่แน่ใจ ตั้งค่าเป็น OpenGL หาก GPU ของคุณไม่รองรับ Vulkan แม้จะมีไดรเวอร์กราฟิกล่าสุดก็ตาม", + "SettingsEnableTextureRecompression": "เปิดใช้งานการบีบอัดพื้นผิวอีกครั้ง", + "SettingsEnableTextureRecompressionTooltip": "บีบอัดพื้นผิว ASTC เพื่อลดการใช้งาน VRAM\n\nเกมที่ใช้รูปแบบพื้นผิวนี้ ได้แก่ Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder และ The Legend of Zelda: Tears of the Kingdom\n\nกราฟิกการ์ดที่มี 4 กิกะไบต์ VRAM หรือน้อยกว่ามีแนวโน้มที่จะให้แคชในบางจุดขณะเล่นเกมเหล่านี้\n\nเปิดใช้งานเฉพาะในกรณีที่ VRAM ของคุณใกล้หมดในเกมที่กล่าวมาข้างต้น ปล่อยให้ปิดหากไม่แน่ใจ", + "SettingsTabGraphicsPreferredGpu": "GPU ที่ต้องการ", + "SettingsTabGraphicsPreferredGpuTooltip": "เลือกกราฟิกการ์ดที่จะใช้กับแบ็กเอนด์กราฟิก Vulkan\n\nไม่ส่งผลต่อ GPU ที่ OpenGL จะใช้\n\nตั้งค่าเป็น GPU ที่ถูกตั้งค่าสถานะเป็น \"dGPU\" หากคุณไม่แน่ใจ หากไม่มีก็ปล่อยทิ้งไว้โดยไม่มีใครแตะต้องมัน", + "SettingsAppRequiredRestartMessage": "จำเป็นต้องรีสตาร์ท รียูจินซ์", + "SettingsGpuBackendRestartMessage": "การตั้งค่ากราฟิกแบ็กเอนด์หรือ GPU ได้รับการแก้ไขแล้ว สิ่งนี้จะต้องมีการรีสตาร์ทจึงจะสามารถใช้งานได้", + "SettingsGpuBackendRestartSubMessage": "คุณต้องการรีสตาร์ทตอนนี้หรือไม่?", + "RyujinxUpdaterMessage": "คุณต้องการอัพเดต รียูจินซ์ เป็นเวอร์ชั่นล่าสุดหรือไม่?", + "SettingsTabHotkeysVolumeUpHotkey": "เพิ่มระดับเสียง:", + "SettingsTabHotkeysVolumeDownHotkey": "ลดระดับเสียง:", + "SettingsEnableMacroHLE": "เปิดใช้งาน มาโคร HLE", + "SettingsEnableMacroHLETooltip": "การจำลองระดับสูงของโค้ดมาโคร GPU\n\nปรับปรุงประสิทธิภาพ แต่อาจทำให้เกิดข้อผิดพลาดด้านกราฟิกในบางเกม\n\nปล่อยไว้หากคุณไม่แน่ใจ", + "SettingsEnableColorSpacePassthrough": "ทะลุผ่านพื้นที่สี", + "SettingsEnableColorSpacePassthroughTooltip": "สั่งให้แบ็กเอนด์ Vulkan ส่งผ่านข้อมูลสีโดยไม่ต้องระบุค่าของสี สำหรับผู้ใช้ที่มีการแสดงกระจายตัวของสี อาจส่งผลให้สีสดใสมากขึ้น โดยต้องแลกกับความถูกต้องของสี", + "VolumeShort": "ระดับเสียง", + "UserProfilesManageSaves": "จัดการบันทึก", + "DeleteUserSave": "คุณต้องการลบบันทึกผู้ใช้สำหรับเกมนี้หรือไม่?", + "IrreversibleActionNote": "การดำเนินการนี้ไม่สามารถย้อนกลับได้", + "SaveManagerHeading": "จัดการบันทึกสำหรับ {0} ({1})", + "SaveManagerTitle": "จัดการบันทึก", + "Name": "ชื่อ", + "Size": "ขนาด", + "Search": "ค้นหา", + "UserProfilesRecoverLostAccounts": "กู้คืนบัญชีที่สูญหาย", + "Recover": "กู้คืน", + "UserProfilesRecoverHeading": "พบบันทึกสำหรับบัญชีดังต่อไปนี้", + "UserProfilesRecoverEmptyList": "ไม่มีโปรไฟล์ที่สามารถกู้คืนได้", + "GraphicsAATooltip": "ใช้การลดรอยหยักกับการเรนเดอร์เกม\n\nFXAA จะเบลอภาพส่วนใหญ่ ในขณะที่ SMAA จะพยายามค้นหาขอบหยักและปรับให้เรียบ\n\nไม่แนะนำให้ใช้ร่วมกับตัวกรองสเกล FSR\n\nตัวเลือกนี้สามารถเปลี่ยนแปลงได้ในขณะที่เกมกำลังทำงานอยู่โดยคลิก \"นำไปใช้\" ด้านล่าง คุณสามารถย้ายหน้าต่างการตั้งค่าไปด้านข้างและทดลองจนกว่าคุณจะพบรูปลักษณ์ที่คุณต้องการสำหรับเกม\n\nปล่อยไว้ที่ NONE หากไม่แน่ใจ", + "GraphicsAALabel": "ลดการฉีกขาดของภาพ:", + "GraphicsScalingFilterLabel": "ปรับขนาดตัวกรอง:", + "GraphicsScalingFilterTooltip": "เลือกตัวกรองสเกลที่จะใช้เมื่อใช้สเกลความละเอียด\n\nBilinear ทำงานได้ดีกับเกม 3D และเป็นตัวเลือกเริ่มต้นที่ปลอดภัย\n\nแนะนำให้ใช้เกมภาพพิกเซลที่ใกล้เคียงที่สุด\n\nFSR 1.0 เป็นเพียงตัวกรองความคมชัด ไม่แนะนำให้ใช้กับ FXAA หรือ SMAA\n\nตัวเลือกนี้สามารถเปลี่ยนแปลงได้ในขณะที่เกมกำลังทำงานอยู่โดยคลิก \"นำไปใช้\" ด้านล่าง คุณสามารถย้ายหน้าต่างการตั้งค่าไปด้านข้างและทดลองจนกว่าคุณจะพบรูปลักษณ์ที่คุณต้องการสำหรับเกม", + "GraphicsScalingFilterLevelLabel": "ระดับ", + "GraphicsScalingFilterLevelTooltip": "ตั้งค่าระดับความคมชัด FSR 1.0 สูงกว่าจะคมชัดกว่า", + "SmaaLow": "SMAA ต่ำ", + "SmaaMedium": "SMAA ปานกลาง", + "SmaaHigh": "SMAA สูง", + "SmaaUltra": "SMAA สูงมาก", + "UserEditorTitle": "แก้ไขผู้ใช้", + "UserEditorTitleCreate": "สร้างผู้ใช้", + "SettingsTabNetworkInterface": "เชื่อมต่อเครือข่าย:", + "NetworkInterfaceTooltip": "อินเทอร์เฟซเครือข่ายที่ใช้สำหรับคุณสมบัติ LAN/LDN\n\nเมื่อใช้ร่วมกับ VPN หรือ XLink Kai และเกมที่รองรับ LAN สามารถใช้เพื่อปลอมการเชื่อมต่อเครือข่ายเดียวกันผ่านทางอินเทอร์เน็ต\n\nปล่อยให้เป็น ค่าเริ่มต้น หากคุณไม่แน่ใจ", + "NetworkInterfaceDefault": "ค่าเริ่มต้น", + "PackagingShaders": "แพ็คเชเดอร์ไฟล์", + "AboutChangelogButton": "ดูบันทึกการเปลี่ยนแปลงบน GitHub", + "AboutChangelogButtonTooltipMessage": "คลิกเพื่อเปิดบันทึกการเปลี่ยนแปลงสำหรับเวอร์ชั่นนี้ บนเบราว์เซอร์เริ่มต้นของคุณ", + "SettingsTabNetworkMultiplayer": "ผู้เล่นหลายคน", + "MultiplayerMode": "โหมด:", + "MultiplayerModeTooltip": "เปลี่ยนโหมดผู้เล่นหลายคนของ LDN\n\nLdnMitm จะปรับเปลี่ยนฟังก์ชันการเล่นแบบไร้สาย/ภายใน จะให้เกมทำงานเหมือนกับว่าเป็น LAN ช่วยให้สามารถเชื่อมต่อภายในเครือข่ายเดียวกันกับอินสแตนซ์ Ryujinx อื่น ๆ และคอนโซล Nintendo Switch ที่ถูกแฮ็กซึ่งมีโมดูล ldn_mitm ติดตั้งอยู่\n\nผู้เล่นหลายคนต้องการให้ผู้เล่นทุกคนอยู่ในเกมเวอร์ชันเดียวกัน (เช่น Super Smash Bros. Ultimate v13.0.1 ไม่สามารถเชื่อมต่อกับ v13.0.0)\n\nปล่อยให้ปิดการใช้งานหากไม่แน่ใจ" +} diff --git a/src/Ryujinx/Assets/Locales/tr_TR.json b/src/Ryujinx/Assets/Locales/tr_TR.json index 3f70a781d6..569ed28b1e 100644 --- a/src/Ryujinx/Assets/Locales/tr_TR.json +++ b/src/Ryujinx/Assets/Locales/tr_TR.json @@ -14,7 +14,7 @@ "MenuBarFileOpenEmuFolder": "Ryujinx Klasörünü aç", "MenuBarFileOpenLogsFolder": "Logs Klasörünü aç", "MenuBarFileExit": "_Çıkış", - "MenuBarOptions": "Seçenekler", + "MenuBarOptions": "_Seçenekler", "MenuBarOptionsToggleFullscreen": "Tam Ekran Modunu Aç", "MenuBarOptionsStartGamesInFullscreen": "Oyunları Tam Ekran Modunda Başlat", "MenuBarOptionsStopEmulation": "Emülasyonu Durdur", @@ -30,7 +30,7 @@ "MenuBarToolsManageFileTypes": "Dosya uzantılarını yönet", "MenuBarToolsInstallFileTypes": "Dosya uzantılarını yükle", "MenuBarToolsUninstallFileTypes": "Dosya uzantılarını kaldır", - "MenuBarHelp": "Yardım", + "MenuBarHelp": "_Yardım", "MenuBarHelpCheckForUpdates": "Güncellemeleri Denetle", "MenuBarHelpAbout": "Hakkında", "MenuSearch": "Ara...", @@ -54,8 +54,6 @@ "GameListContextMenuManageTitleUpdatesToolTip": "Oyun Güncelleme Yönetim Penceresini Açar", "GameListContextMenuManageDlc": "DLC'leri Yönet", "GameListContextMenuManageDlcToolTip": "DLC yönetim penceresini açar", - "GameListContextMenuOpenModsDirectory": "Mod Dizinini Aç", - "GameListContextMenuOpenModsDirectoryToolTip": "Uygulamanın modlarının bulunduğu dizini açar", "GameListContextMenuCacheManagement": "Önbellek Yönetimi", "GameListContextMenuCacheManagementPurgePptc": "PPTC Yeniden Yapılandırmasını Başlat", "GameListContextMenuCacheManagementPurgePptcToolTip": "Oyunun bir sonraki açılışında PPTC'yi yeniden yapılandır", @@ -72,6 +70,13 @@ "GameListContextMenuExtractDataRomFSToolTip": "Uygulamanın geçerli yapılandırmasından RomFS kısmını ayıkla (Güncellemeler dahil)", "GameListContextMenuExtractDataLogo": "Simge", "GameListContextMenuExtractDataLogoToolTip": "Uygulamanın geçerli yapılandırmasından Logo kısmını ayıkla (Güncellemeler dahil)", + "GameListContextMenuCreateShortcut": "Uygulama Kısayolu Oluştur", + "GameListContextMenuCreateShortcutToolTip": "Seçilmiş uygulamayı çalıştıracak bir masaüstü kısayolu oluştur", + "GameListContextMenuCreateShortcutToolTipMacOS": "Create a shortcut in macOS's Applications folder that launches the selected Application", + "GameListContextMenuOpenModsDirectory": "Open Mods Directory", + "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", + "GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", "StatusBarGamesLoaded": "{0}/{1} Oyun Yüklendi", "StatusBarSystemVersion": "Sistem Sürümü: {0}", "LinuxVmMaxMapCountDialogTitle": "Bellek Haritaları İçin Düşük Limit Tespit Edildi ", @@ -150,7 +155,7 @@ "SettingsTabGraphicsResolutionScaleNative": "Yerel (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Not recommended)", "SettingsTabGraphicsAspectRatio": "En-Boy Oranı:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -200,9 +205,9 @@ "ControllerSettingsInputDevice": "Giriş Cihazı", "ControllerSettingsRefresh": "Yenile", "ControllerSettingsDeviceDisabled": "Devre Dışı", - "ControllerSettingsControllerType": "Kontrolcü Tipi", + "ControllerSettingsControllerType": "Kumanda Tipi", "ControllerSettingsControllerTypeHandheld": "Portatif Mod", - "ControllerSettingsControllerTypeProController": "Profesyonel Denetleyici", + "ControllerSettingsControllerTypeProController": "Profesyonel Kumanda", "ControllerSettingsControllerTypeJoyConPair": "JoyCon Çifti", "ControllerSettingsControllerTypeJoyConLeft": "JoyCon Sol", "ControllerSettingsControllerTypeJoyConRight": "JoyCon Sağ", @@ -253,7 +258,7 @@ "ControllerSettingsTriggerThreshold": "Tetik Eşiği:", "ControllerSettingsMotion": "Hareket", "ControllerSettingsMotionUseCemuhookCompatibleMotion": "CemuHook uyumlu hareket kullan", - "ControllerSettingsMotionControllerSlot": "Kontrolcü Yuvası:", + "ControllerSettingsMotionControllerSlot": "Kumanda Yuvası:", "ControllerSettingsMotionMirrorInput": "Girişi Aynala", "ControllerSettingsMotionRightJoyConSlot": "Sağ JoyCon Yuvası:", "ControllerSettingsMotionServerHost": "Sunucu Sahibi:", @@ -292,13 +297,9 @@ "GameListContextMenuRunApplication": "Uygulamayı Çalıştır", "GameListContextMenuToggleFavorite": "Favori Ayarla", "GameListContextMenuToggleFavoriteToolTip": "Oyunu Favorilere Ekle/Çıkar", - "SettingsTabGeneralTheme": "Tema", - "SettingsTabGeneralThemeCustomTheme": "Özel Tema Yolu", - "SettingsTabGeneralThemeBaseStyle": "Temel Stil", - "SettingsTabGeneralThemeBaseStyleDark": "Karanlık", - "SettingsTabGeneralThemeBaseStyleLight": "Aydınlık", - "SettingsTabGeneralThemeEnableCustomTheme": "Özel Tema Etkinleştir", - "ButtonBrowse": "Göz At", + "SettingsTabGeneralTheme": "Theme:", + "SettingsTabGeneralThemeDark": "Dark", + "SettingsTabGeneralThemeLight": "Light", "ControllerSettingsConfigureGeneral": "Ayarla", "ControllerSettingsRumble": "Titreşim", "ControllerSettingsRumbleStrongMultiplier": "Güçlü Titreşim Çoklayıcı", @@ -332,8 +333,6 @@ "DialogUpdaterAddingFilesMessage": "Yeni Güncelleme Ekleniyor...", "DialogUpdaterCompleteMessage": "Güncelleme Tamamlandı!", "DialogUpdaterRestartMessage": "Ryujinx'i şimdi yeniden başlatmak istiyor musunuz?", - "DialogUpdaterArchNotSupportedMessage": "Sistem mimariniz desteklenmemektedir!", - "DialogUpdaterArchNotSupportedSubMessage": "(Sadece x64 sistemleri desteklenmektedir!)", "DialogUpdaterNoInternetMessage": "İnternete bağlı değilsiniz!", "DialogUpdaterNoInternetSubMessage": "Lütfen aktif bir internet bağlantınız olduğunu kontrol edin!", "DialogUpdaterDirtyBuildMessage": "Ryujinx'in Dirty build'lerini güncelleyemezsiniz!", @@ -350,7 +349,7 @@ "DialogUninstallFileTypesSuccessMessage": "Dosya uzantıları başarıyla kaldırıldı!", "DialogUninstallFileTypesErrorMessage": "Dosya uzantıları kaldırma işlemi başarısız oldu.", "DialogOpenSettingsWindowLabel": "Seçenekler Penceresini Aç", - "DialogControllerAppletTitle": "Kontrolcü Applet'i", + "DialogControllerAppletTitle": "Kumanda Applet'i", "DialogMessageDialogErrorExceptionMessage": "Mesaj diyaloğu gösterilirken hata: {0}", "DialogSoftwareKeyboardErrorExceptionMessage": "Mesaj diyaloğu gösterilirken hata: {0}", "DialogErrorAppletErrorExceptionMessage": "Applet diyaloğu gösterilirken hata: {0}", @@ -383,9 +382,12 @@ "DialogUserProfileUnsavedChangesTitle": "Uyarı - Kaydedilmemiş Değişiklikler", "DialogUserProfileUnsavedChangesMessage": "Kullanıcı profilinizde kaydedilmemiş değişiklikler var.", "DialogUserProfileUnsavedChangesSubMessage": "Yaptığınız değişiklikleri iptal etmek istediğinize emin misiniz?", - "DialogControllerSettingsModifiedConfirmMessage": "Güncel kontrolcü seçenekleri güncellendi.", + "DialogControllerSettingsModifiedConfirmMessage": "Geçerli kumanda seçenekleri güncellendi.", "DialogControllerSettingsModifiedConfirmSubMessage": "Kaydetmek istiyor musunuz?", - "DialogLoadNcaErrorMessage": "{0}. Hatalı Dosya: {1}", + "DialogLoadFileErrorMessage": "{0}. Errored File: {1}", + "DialogModAlreadyExistsMessage": "Mod already exists", + "DialogModInvalidMessage": "The specified directory does not contain a mod!", + "DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!", "DialogDlcNoDlcErrorMessage": "Belirtilen dosya seçilen oyun için DLC içermiyor!", "DialogPerformanceCheckLoggingEnabledMessage": "Sadece geliştiriler için dizayn edilen Trace Loglama seçeneği etkin.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "En iyi performans için trace loglama'nın devre dışı bırakılması tavsiye edilir. Trace loglama seçeneğini şimdi devre dışı bırakmak ister misiniz?", @@ -396,6 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "Belirtilen dosya seçilen oyun için güncelleme içermiyor!", "DialogSettingsBackendThreadingWarningTitle": "Uyarı - Backend Threading", "DialogSettingsBackendThreadingWarningMessage": "Bu seçeneğin tamamen uygulanması için Ryujinx'in kapatıp açılması gerekir. Kullandığınız işletim sistemine bağlı olarak, Ryujinx'in multithreading'ini kullanırken driver'ınızın multithreading seçeneğini kapatmanız gerekebilir.", + "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", "SettingsTabGraphicsFeaturesOptions": "Özellikler", "SettingsTabGraphicsBackendMultithreading": "Grafik Backend Multithreading:", "CommonAuto": "Otomatik", @@ -430,6 +434,7 @@ "DlcManagerRemoveAllButton": "Tümünü kaldır", "DlcManagerEnableAllButton": "Tümünü Aktif Et", "DlcManagerDisableAllButton": "Tümünü Devre Dışı Bırak", + "ModManagerDeleteAllButton": "Delete All", "MenuBarOptionsChangeLanguage": "Dili Değiştir", "MenuBarShowFileTypes": "Dosya Uzantılarını Göster", "CommonSort": "Sırala", @@ -446,14 +451,14 @@ "CustomThemeCheckTooltip": "Emülatör pencerelerinin görünümünü değiştirmek için özel bir Avalonia teması kullan", "CustomThemePathTooltip": "Özel arayüz temasının yolu", "CustomThemeBrowseTooltip": "Özel arayüz teması için göz at", - "DockModeToggleTooltip": "Docked modu emüle edilen sistemin yerleşik Nintendo Switch gibi davranmasını sağlar. Bu çoğu oyunda grafik kalitesini arttırır. Diğer yandan, bu seçeneği devre dışı bırakmak emüle edilen sistemin elde Ninendo Switch gibi davranmasını sağlayıp grafik kalitesini düşürür.\n\nDocked modu kullanmayı düşünüyorsanız 1. Oyuncu kontrollerini; Handheld modunu kullanmak istiyorsanız Handheld kontrollerini konfigüre edin.\n\nEmin değilseniz aktif halde bırakın.", - "DirectKeyboardTooltip": "Doğrudan Klavye Erişimi (HID) desteği. Oyunların klavyenizi metin giriş cihazı olarak kullanmasını sağlar.", - "DirectMouseTooltip": "Doğrudan Fare Erişimi (HID) desteği. Oyunların farenizi işaret aygıtı olarak kullanmasını sağlar.", + "DockModeToggleTooltip": "Docked modu emüle edilen sistemin yerleşik Nintendo Switch gibi davranmasını sağlar. Bu çoğu oyunda grafik kalitesini arttırır. Diğer yandan, bu seçeneği devre dışı bırakmak emüle edilen sistemin portatif Ninendo Switch gibi davranmasını sağlayıp grafik kalitesini düşürür.\n\nDocked modu kullanmayı düşünüyorsanız 1. Oyuncu kontrollerini; Handheld modunu kullanmak istiyorsanız portatif kontrollerini konfigüre edin.\n\nEmin değilseniz aktif halde bırakın.", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", "RegionTooltip": "Sistem Bölgesini Değiştir", "LanguageTooltip": "Sistem Dilini Değiştir", "TimezoneTooltip": "Sistem Saat Dilimini Değiştir", "TimeTooltip": "Sistem Saatini Değiştir", - "VSyncToggleTooltip": "Emüle edilen konsolun Dikey Senkronizasyonu. Çoğu oyun için kare sınırlayıcı işlevi görür, bu seçeneği devre dışı bırakmak bazı oyunların normalden yüksek hızda çalışmasını ve yükleme ekranlarının daha uzun sürmesini veya sıkışıp kalmasını sağlar.\n\nTercih ettiğiniz bir kısayol ile oyun içindeyken etkinleştirilip devre dışı bırakılabilir. Bu seçeneği devre dışı bırakmayı düşünüyorsanız bir kısayol atamanızı öneririz.\n\nEmin değilseniz aktif halde bırakın.", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", "PptcToggleTooltip": "Çevrilen JIT fonksiyonlarını oyun her açıldığında çevrilmek zorunda kalmaması için kaydeder.\n\nTeklemeyi azaltır ve ilk açılıştan sonra oyunların ilk açılış süresini ciddi biçimde hızlandırır.\n\nEmin değilseniz aktif halde bırakın.", "FsIntegrityToggleTooltip": "Oyun açarken hatalı dosyaların olup olmadığını kontrol eder, ve hatalı dosya bulursa log dosyasında hash hatası görüntüler.\n\nPerformansa herhangi bir etkisi yoktur ve sorun gidermeye yardımcı olur.\n\nEmin değilseniz aktif halde bırakın.", "AudioBackendTooltip": "Ses çıkış motorunu değiştirir.\n\nSDL2 tercih edilen seçenektir, OpenAL ve SoundIO ise alternatif olarak kullanılabilir. Dummy seçeneğinde ses çıkışı olmayacaktır.\n\nEmin değilseniz SDL2 seçeneğine ayarlayın.", @@ -467,10 +472,10 @@ "GraphicsBackendThreadingTooltip": "Grafik arka uç komutlarını ikinci bir iş parçacığında işletir.\n\nKendi multithreading desteği olmayan sürücülerde shader derlemeyi hızlandırıp performansı artırır. Multithreading desteği olan sürücülerde çok az daha iyi performans sağlar.\n\nEmin değilseniz Otomatik seçeneğine ayarlayın.", "GalThreadingTooltip": "Grafik arka uç komutlarını ikinci bir iş parçacığında işletir.\n\nKendi multithreading desteği olmayan sürücülerde shader derlemeyi hızlandırıp performansı artırır. Multithreading desteği olan sürücülerde çok az daha iyi performans sağlar.\n\nEmin değilseniz Otomatik seçeneğine ayarlayın.", "ShaderCacheToggleTooltip": "Sonraki çalışmalarda takılmaları engelleyen bir gölgelendirici disk önbelleğine kaydeder.", - "ResolutionScaleTooltip": "Uygulanabilir grafik hedeflerine uygulanan çözünürlük ölçeği", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", "ResolutionScaleEntryTooltip": "Küsüratlı çözünürlük ölçeği, 1.5 gibi. Küsüratlı ölçekler hata oluşturmaya ve çökmeye daha yatkındır.", - "AnisotropyTooltip": "Eşyönsüz doku süzmesi seviyesi (Oyun tarafından istenen değeri kullanmak için Otomatik seçeneğine ayarlayın)", - "AspectRatioTooltip": "Grafik penceresine uygulanan en-boy oranı.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", "ShaderDumpPathTooltip": "Grafik Shader Döküm Yolu", "FileLogTooltip": "Konsol loglarını diskte bir log dosyasına kaydeder. Performansı etkilemez.", "StubLogTooltip": "Stub log mesajlarını konsola yazdırır. Performansı etkilemez.", @@ -504,6 +509,8 @@ "EnableInternetAccessTooltip": "Emüle edilen uygulamanın internete bağlanmasını sağlar.\n\nLAN modu bulunan oyunlar bu seçenek ile birbirine bağlanabilir ve sistemler aynı access point'e bağlanır. Bu gerçek konsolları da kapsar.\n\nNintendo sunucularına bağlanmayı sağlaMAZ. Internete bağlanmaya çalışan baz oyunların çökmesine sebep olabilr.\n\nEmin değilseniz devre dışı bırakın.", "GameListContextMenuManageCheatToolTip": "Hileleri yönetmeyi sağlar", "GameListContextMenuManageCheat": "Hileleri Yönet", + "GameListContextMenuManageModToolTip": "Manage Mods", + "GameListContextMenuManageMod": "Manage Mods", "ControllerSettingsStickRange": "Menzil:", "DialogStopEmulationTitle": "Ryujinx - Emülasyonu Durdur", "DialogStopEmulationMessage": "Emülasyonu durdurmak istediğinizden emin misiniz?", @@ -515,8 +522,6 @@ "SettingsTabCpuMemory": "CPU Hafızası", "DialogUpdaterFlatpakNotSupportedMessage": "Lütfen Ryujinx'i FlatHub aracılığıyla güncelleyin.", "UpdaterDisabledWarningTitle": "Güncelleyici Devre Dışı!", - "GameListContextMenuOpenSdModsDirectory": "Atmosphere Mod Dizini", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Uygulama Modlarını içeren alternatif SD kart Atmosfer dizinini açar. Gerçek donanım için paketlenmiş modlar için kullanışlıdır.", "ControllerSettingsRotate90": "Saat yönünde 90° Döndür", "IconSize": "Ikon Boyutu", "IconSizeTooltip": "Oyun ikonlarının boyutunu değiştirmeyi sağlar", @@ -544,19 +549,20 @@ "SwkbdMinCharacters": "En az {0} karakter uzunluğunda olmalı", "SwkbdMinRangeCharacters": "{0}-{1} karakter uzunluğunda olmalı", "SoftwareKeyboard": "Yazılım Klavyesi", - "SoftwareKeyboardModeNumbersOnly": "Sadece Numara Olabilir", + "SoftwareKeyboardModeNumeric": "Sadece 0-9 veya '.' olabilir", "SoftwareKeyboardModeAlphabet": "Sadece CJK-characters olmayan karakterler olabilir", "SoftwareKeyboardModeASCII": "Sadece ASCII karakterler olabilir", - "DialogControllerAppletMessagePlayerRange": "Uygulama belirtilen türde {0} oyuncu istiyor:\n\nTÜRLER: {1}\n\nOYUNCULAR: {2}\n\n{3}Lütfen şimdi seçeneklerden giriş aygıtlarını ayarlayın veya Kapat'a basın.", - "DialogControllerAppletMessage": "Uygulama belirtilen türde tam olarak {0} oyuncu istiyor:\n\nTÜRLER: {1}\n\nOYUNCULAR: {2}\n\n{3}Lütfen şimdi seçeneklerden giriş aygıtlarını ayarlayın veya Kapat'a basın.", - "DialogControllerAppletDockModeSet": "Docked mode etkin. Handheld geçersiz.\n\n", + "ControllerAppletControllers": "Desteklenen Kumandalar:", + "ControllerAppletPlayers": "Oyuncular:", + "ControllerAppletDescription": "Halihazırdaki konfigürasyonunuz geçersiz. Ayarları açın ve girişlerinizi yeniden konfigüre edin.", + "ControllerAppletDocked": "Docked mod ayarlandı. Portatif denetim devre dışı bırakılmalı.", "UpdaterRenaming": "Eski dosyalar yeniden adlandırılıyor...", "UpdaterRenameFailed": "Güncelleyici belirtilen dosyayı yeniden adlandıramadı: {0}", "UpdaterAddingFiles": "Yeni Dosyalar Ekleniyor...", "UpdaterExtracting": "Güncelleme Ayrıştırılıyor...", "UpdaterDownloading": "Güncelleme İndiriliyor...", "Game": "Oyun", - "Docked": "Yerleştirildi", + "Docked": "Docked", "Handheld": "El tipi", "ConnectionError": "Bağlantı Hatası.", "AboutPageDeveloperListMore": "{0} ve daha fazla...", @@ -587,6 +593,7 @@ "Writable": "Yazılabilir", "SelectDlcDialogTitle": "DLC dosyalarını seç", "SelectUpdateDialogTitle": "Güncelleme dosyalarını seç", + "SelectModDialogTitle": "Select mod directory", "UserProfileWindowTitle": "Kullanıcı Profillerini Yönet", "CheatWindowTitle": "Oyun Hilelerini Yönet", "DlcWindowTitle": "Oyun DLC'lerini Yönet", @@ -594,10 +601,12 @@ "CheatWindowHeading": "{0} için Hile mevcut [{1}]", "BuildId": "BuildId:", "DlcWindowHeading": "{0} için DLC mevcut [{1}]", + "ModWindowHeading": "{0} Mod(s)", "UserProfilesEditProfile": "Seçiliyi Düzenle", "Cancel": "İptal", "Save": "Kaydet", "Discard": "Iskarta", + "Paused": "Durduruldu", "UserProfilesSetProfileImage": "Profil Resmi Ayarla", "UserProfileEmptyNameError": "İsim gerekli", "UserProfileNoImageError": "Profil resmi ayarlanmalıdır", @@ -607,9 +616,9 @@ "UserProfilesName": "İsim:", "UserProfilesUserId": "Kullanıcı Adı:", "SettingsTabGraphicsBackend": "Grafik Arka Ucu", - "SettingsTabGraphicsBackendTooltip": "Kullanılacak Grafik Arka Uç", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", "SettingsEnableTextureRecompression": "Yeniden Doku Sıkıştırılmasını Aktif Et", - "SettingsEnableTextureRecompressionTooltip": "4GB VRAM'in Altında Sistemler için önerilir.\n\nEmin değilseniz kapalı bırakın", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", "SettingsTabGraphicsPreferredGpu": "Kullanılan GPU", "SettingsTabGraphicsPreferredGpuTooltip": "Vulkan Grafik Arka Ucu ile kullanılacak Ekran Kartını Seçin.\n\nOpenGL'nin kullanacağı GPU'yu etkilemez.\n\n Emin değilseniz \"dGPU\" olarak işaretlenmiş GPU'ya ayarlayın. Eğer yoksa, dokunmadan bırakın.\n", "SettingsAppRequiredRestartMessage": "Ryujinx'i Yeniden Başlatma Gerekli", @@ -635,12 +644,12 @@ "Recover": "Kurtar", "UserProfilesRecoverHeading": "Aşağıdaki hesaplar için kayıtlar bulundu", "UserProfilesRecoverEmptyList": "Kurtarılacak profil bulunamadı", - "GraphicsAATooltip": "Oyuna Kenar Yumuşatma Ekler", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", "GraphicsAALabel": "Kenar Yumuşatma:", "GraphicsScalingFilterLabel": "Ölçekleme Filtresi:", - "GraphicsScalingFilterTooltip": "Çerçeve Arabellek Filtresini Açar", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", "GraphicsScalingFilterLevelLabel": "Seviye", - "GraphicsScalingFilterLevelTooltip": "Ölçekleme Filtre Seviyesini Belirle", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "Düşük SMAA", "SmaaMedium": "Orta SMAA", "SmaaHigh": "Yüksek SMAA", @@ -648,9 +657,12 @@ "UserEditorTitle": "Kullanıcıyı Düzenle", "UserEditorTitleCreate": "Kullanıcı Oluştur", "SettingsTabNetworkInterface": "Ağ Bağlantısı:", - "NetworkInterfaceTooltip": "LAN özellikleri için kullanılan ağ bağlantısı", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", "NetworkInterfaceDefault": "Varsayılan", "PackagingShaders": "Gölgeler Paketleniyor", "AboutChangelogButton": "GitHub'da Değişiklikleri Görüntüle", - "AboutChangelogButtonTooltipMessage": "Kullandığınız versiyon için olan değişiklikleri varsayılan tarayıcınızda görmek için tıklayın" -} \ No newline at end of file + "AboutChangelogButtonTooltipMessage": "Kullandığınız versiyon için olan değişiklikleri varsayılan tarayıcınızda görmek için tıklayın", + "SettingsTabNetworkMultiplayer": "Çok Oyunculu", + "MultiplayerMode": "Mod:", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." +} diff --git a/src/Ryujinx/Assets/Locales/uk_UA.json b/src/Ryujinx/Assets/Locales/uk_UA.json index a119fb4b71..61af5c32a5 100644 --- a/src/Ryujinx/Assets/Locales/uk_UA.json +++ b/src/Ryujinx/Assets/Locales/uk_UA.json @@ -1,46 +1,46 @@ { - "Language": "Yкраїнська", + "Language": "Українська", "MenuBarFileOpenApplet": "Відкрити аплет", - "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Відкрийте аплет Mii Editor в автономному режимі", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Відкрити аплет Mii Editor в автономному режимі", "SettingsTabInputDirectMouseAccess": "Прямий доступ мишею", - "SettingsTabSystemMemoryManagerMode": "Режим диспетчера пам'яті:", + "SettingsTabSystemMemoryManagerMode": "Режим диспетчера пам’яті:", "SettingsTabSystemMemoryManagerModeSoftware": "Програмне забезпечення", "SettingsTabSystemMemoryManagerModeHost": "Хост (швидко)", "SettingsTabSystemMemoryManagerModeHostUnchecked": "Неперевірений хост (найшвидший, небезпечний)", - "SettingsTabSystemUseHypervisor": "Use Hypervisor", + "SettingsTabSystemUseHypervisor": "Використовувати гіпервізор", "MenuBarFile": "_Файл", "MenuBarFileOpenFromFile": "_Завантажити програму з файлу", "MenuBarFileOpenUnpacked": "Завантажити _розпаковану гру", "MenuBarFileOpenEmuFolder": "Відкрити теку Ryujinx", "MenuBarFileOpenLogsFolder": "Відкрити теку журналів змін", "MenuBarFileExit": "_Вихід", - "MenuBarOptions": "Опції", - "MenuBarOptionsToggleFullscreen": "Перемкнути на весь екран", + "MenuBarOptions": "_Options", + "MenuBarOptionsToggleFullscreen": "На весь екран", "MenuBarOptionsStartGamesInFullscreen": "Запускати ігри на весь екран", "MenuBarOptionsStopEmulation": "Зупинити емуляцію", "MenuBarOptionsSettings": "_Налаштування", - "MenuBarOptionsManageUserProfiles": "_Керування профілями користувачів", + "MenuBarOptionsManageUserProfiles": "_Керувати профілями користувачів", "MenuBarActions": "_Дії", "MenuBarOptionsSimulateWakeUpMessage": "Симулювати повідомлення про пробудження", "MenuBarActionsScanAmiibo": "Сканувати Amiibo", "MenuBarTools": "_Інструменти", - "MenuBarToolsInstallFirmware": "Встановити прошивку", - "MenuBarFileToolsInstallFirmwareFromFile": "Встановити прошивку з XCI або ZIP", - "MenuBarFileToolsInstallFirmwareFromDirectory": "Встановити прошивку з теки", - "MenuBarToolsManageFileTypes": "Manage file types", - "MenuBarToolsInstallFileTypes": "Install file types", - "MenuBarToolsUninstallFileTypes": "Uninstall file types", - "MenuBarHelp": "Довідка", + "MenuBarToolsInstallFirmware": "Установити прошивку", + "MenuBarFileToolsInstallFirmwareFromFile": "Установити прошивку з XCI або ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Установити прошивку з теки", + "MenuBarToolsManageFileTypes": "Керувати типами файлів", + "MenuBarToolsInstallFileTypes": "Установити типи файлів", + "MenuBarToolsUninstallFileTypes": "Видалити типи файлів", + "MenuBarHelp": "_Help", "MenuBarHelpCheckForUpdates": "Перевірити оновлення", - "MenuBarHelpAbout": "Про програму", + "MenuBarHelpAbout": "Про застосунок", "MenuSearch": "Пошук...", - "GameListHeaderFavorite": "Вибране", + "GameListHeaderFavorite": "Обране", "GameListHeaderIcon": "Значок", "GameListHeaderApplication": "Назва", "GameListHeaderDeveloper": "Розробник", "GameListHeaderVersion": "Версія", "GameListHeaderTimePlayed": "Зіграно часу", - "GameListHeaderLastPlayed": "Остання гра", + "GameListHeaderLastPlayed": "Востаннє зіграно", "GameListHeaderFileExtension": "Розширення файлу", "GameListHeaderFileSize": "Розмір файлу", "GameListHeaderPath": "Шлях", @@ -54,8 +54,6 @@ "GameListContextMenuManageTitleUpdatesToolTip": "Відкриває вікно керування оновленням заголовка", "GameListContextMenuManageDlc": "Керування DLC", "GameListContextMenuManageDlcToolTip": "Відкриває вікно керування DLC", - "GameListContextMenuOpenModsDirectory": "Відкрити каталог модифікацій", - "GameListContextMenuOpenModsDirectoryToolTip": "Відкриває каталог, який містить модифікації програм", "GameListContextMenuCacheManagement": "Керування кешем", "GameListContextMenuCacheManagementPurgePptc": "Очистити кеш PPTC", "GameListContextMenuCacheManagementPurgePptcToolTip": "Видаляє кеш PPTC програми", @@ -72,26 +70,33 @@ "GameListContextMenuExtractDataRomFSToolTip": "Видобуває розділ RomFS із поточної конфігурації програми (включаючи оновлення)", "GameListContextMenuExtractDataLogo": "Логотип", "GameListContextMenuExtractDataLogoToolTip": "Видобуває розділ логотипу з поточної конфігурації програми (включаючи оновлення)", - "StatusBarGamesLoaded": "{0}/{1} Ігор завантажено", + "GameListContextMenuCreateShortcut": "Створити ярлик застосунку", + "GameListContextMenuCreateShortcutToolTip": "Створити ярлик на робочому столі, який запускає вибраний застосунок", + "GameListContextMenuCreateShortcutToolTipMacOS": "Create a shortcut in macOS's Applications folder that launches the selected Application", + "GameListContextMenuOpenModsDirectory": "Відкрити теку з модами", + "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", + "GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", + "StatusBarGamesLoaded": "{0}/{1} ігор завантажено", "StatusBarSystemVersion": "Версія системи: {0}", - "LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected", - "LinuxVmMaxMapCountDialogTextPrimary": "Would you like to increase the value of vm.max_map_count to {0}", - "LinuxVmMaxMapCountDialogTextSecondary": "Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.", - "LinuxVmMaxMapCountDialogButtonUntilRestart": "Yes, until the next restart", - "LinuxVmMaxMapCountDialogButtonPersistent": "Yes, permanently", - "LinuxVmMaxMapCountWarningTextPrimary": "Max amount of memory mappings is lower than recommended.", - "LinuxVmMaxMapCountWarningTextSecondary": "The current value of vm.max_map_count ({0}) is lower than {1}. Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.\n\nYou might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that.", + "LinuxVmMaxMapCountDialogTitle": "Виявлено низьку межу для відображення памʼяті", + "LinuxVmMaxMapCountDialogTextPrimary": "Бажаєте збільшити значення vm.max_map_count на {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "Деякі ігри можуть спробувати створити більше відображень памʼяті, ніж дозволено наразі. Ryujinx завершить роботу, щойно цей ліміт буде перевищено.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Так, до наст. перезапуску", + "LinuxVmMaxMapCountDialogButtonPersistent": "Так, назавжди", + "LinuxVmMaxMapCountWarningTextPrimary": "Максимальна кількість відображення памʼяті менша, ніж рекомендовано.", + "LinuxVmMaxMapCountWarningTextSecondary": "Поточне значення vm.max_map_count ({0}) менше за {1}. Деякі ігри можуть спробувати створити більше відображень пам’яті, ніж дозволено наразі. Ryujinx завершить роботу, щойно цей ліміт буде перевищено.\n\nВи можете збільшити ліміт вручну або встановити pkexec, який дозволяє Ryujinx допомогти з цим.", "Settings": "Налаштування", "SettingsTabGeneral": "Інтерфейс користувача", "SettingsTabGeneralGeneral": "Загальні", "SettingsTabGeneralEnableDiscordRichPresence": "Увімкнути розширену присутність Discord", "SettingsTabGeneralCheckUpdatesOnLaunch": "Перевіряти наявність оновлень під час запуску", "SettingsTabGeneralShowConfirmExitDialog": "Показати діалогове вікно «Підтвердити вихід».", - "SettingsTabGeneralHideCursor": "Hide Cursor:", - "SettingsTabGeneralHideCursorNever": "Never", - "SettingsTabGeneralHideCursorOnIdle": "Приховати курсор у режимі очікування", - "SettingsTabGeneralHideCursorAlways": "Always", - "SettingsTabGeneralGameDirectories": "Каталоги ігор", + "SettingsTabGeneralHideCursor": "Сховати вказівник:", + "SettingsTabGeneralHideCursorNever": "Ніколи", + "SettingsTabGeneralHideCursorOnIdle": "Сховати у режимі очікування", + "SettingsTabGeneralHideCursorAlways": "Завжди", + "SettingsTabGeneralGameDirectories": "Тека ігор", "SettingsTabGeneralAdd": "Додати", "SettingsTabGeneralRemove": "Видалити", "SettingsTabSystem": "Система", @@ -150,7 +155,7 @@ "SettingsTabGraphicsResolutionScaleNative": "Стандартний (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Not recommended)", "SettingsTabGraphicsAspectRatio": "Співвідношення сторін:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -158,7 +163,7 @@ "SettingsTabGraphicsAspectRatio21x9": "21:9", "SettingsTabGraphicsAspectRatio32x9": "32:9", "SettingsTabGraphicsAspectRatioStretch": "Розтягнути до розміру вікна", - "SettingsTabGraphicsDeveloperOptions": "Налаштування виробника", + "SettingsTabGraphicsDeveloperOptions": "Параметри розробника", "SettingsTabGraphicsShaderDumpPath": "Шлях скидання графічного шейдера:", "SettingsTabLogging": "Налагодження", "SettingsTabLoggingLogging": "Налагодження", @@ -172,9 +177,9 @@ "SettingsTabLoggingEnableFsAccessLogs": "Увімкнути журнали доступу Fs", "SettingsTabLoggingFsGlobalAccessLogMode": "Режим журналу глобального доступу Fs:", "SettingsTabLoggingDeveloperOptions": "Параметри розробника (УВАГА: знизиться продуктивність)", - "SettingsTabLoggingDeveloperOptionsNote": "WARNING: Will reduce performance", + "SettingsTabLoggingDeveloperOptionsNote": "УВАГА: Знижує продуктивність", "SettingsTabLoggingGraphicsBackendLogLevel": "Рівень журналу графічного сервера:", - "SettingsTabLoggingGraphicsBackendLogLevelNone": "Ні", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Немає", "SettingsTabLoggingGraphicsBackendLogLevelError": "Помилка", "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Уповільнення", "SettingsTabLoggingGraphicsBackendLogLevelAll": "Все", @@ -223,15 +228,15 @@ "ControllerSettingsDPadDown": "Вниз", "ControllerSettingsDPadLeft": "Вліво", "ControllerSettingsDPadRight": "Вправо", - "ControllerSettingsStickButton": "Button", - "ControllerSettingsStickUp": "Up", - "ControllerSettingsStickDown": "Down", - "ControllerSettingsStickLeft": "Left", - "ControllerSettingsStickRight": "Right", - "ControllerSettingsStickStick": "Stick", - "ControllerSettingsStickInvertXAxis": "Invert Stick X", - "ControllerSettingsStickInvertYAxis": "Invert Stick Y", - "ControllerSettingsStickDeadzone": "Deadzone:", + "ControllerSettingsStickButton": "Кнопка", + "ControllerSettingsStickUp": "Уверх", + "ControllerSettingsStickDown": "Униз", + "ControllerSettingsStickLeft": "Ліворуч", + "ControllerSettingsStickRight": "Праворуч", + "ControllerSettingsStickStick": "Стик", + "ControllerSettingsStickInvertXAxis": "Обернути вісь стику X", + "ControllerSettingsStickInvertYAxis": "Обернути вісь стику Y", + "ControllerSettingsStickDeadzone": "Мертва зона:", "ControllerSettingsLStick": "Лівий джойстик", "ControllerSettingsRStick": "Правий джойстик", "ControllerSettingsTriggersLeft": "Тригери ліворуч", @@ -266,9 +271,9 @@ "UserProfilesChangeProfileImage": "Змінити зображення профілю", "UserProfilesAvailableUserProfiles": "Доступні профілі користувачів:", "UserProfilesAddNewProfile": "Створити профіль", - "UserProfilesDelete": "Delete", + "UserProfilesDelete": "Видалити", "UserProfilesClose": "Закрити", - "ProfileNameSelectionWatermark": "Choose a nickname", + "ProfileNameSelectionWatermark": "Оберіть псевдонім", "ProfileImageSelectionTitle": "Вибір зображення профілю", "ProfileImageSelectionHeader": "Виберіть зображення профілю", "ProfileImageSelectionNote": "Ви можете імпортувати власне зображення профілю або вибрати аватар із мікропрограми системи", @@ -289,16 +294,12 @@ "ControllerSettingsSaveProfileToolTip": "Зберегти профіль", "MenuBarFileToolsTakeScreenshot": "Зробити знімок екрана", "MenuBarFileToolsHideUi": "Сховати інтерфейс", - "GameListContextMenuRunApplication": "Run Application", + "GameListContextMenuRunApplication": "Запустити додаток", "GameListContextMenuToggleFavorite": "Перемкнути вибране", "GameListContextMenuToggleFavoriteToolTip": "Перемкнути улюблений статус гри", - "SettingsTabGeneralTheme": "Тема", - "SettingsTabGeneralThemeCustomTheme": "Користувацький шлях до теми", - "SettingsTabGeneralThemeBaseStyle": "Базовий стиль", - "SettingsTabGeneralThemeBaseStyleDark": "Темна", - "SettingsTabGeneralThemeBaseStyleLight": "Світла", - "SettingsTabGeneralThemeEnableCustomTheme": "Увімкнути користуваьку тему", - "ButtonBrowse": "Огляд", + "SettingsTabGeneralTheme": "Тема:", + "SettingsTabGeneralThemeDark": "Темна", + "SettingsTabGeneralThemeLight": "Світла", "ControllerSettingsConfigureGeneral": "Налаштування", "ControllerSettingsRumble": "Вібрація", "ControllerSettingsRumbleStrongMultiplier": "Множник сильної вібрації", @@ -332,8 +333,6 @@ "DialogUpdaterAddingFilesMessage": "Додавання нового оновлення...", "DialogUpdaterCompleteMessage": "Оновлення завершено!", "DialogUpdaterRestartMessage": "Перезапустити Ryujinx зараз?", - "DialogUpdaterArchNotSupportedMessage": "Ви використовуєте не підтримувану архітектуру системи!", - "DialogUpdaterArchNotSupportedSubMessage": "(Підтримуються лише системи x64!)", "DialogUpdaterNoInternetMessage": "Ви не підключені до Інтернету!", "DialogUpdaterNoInternetSubMessage": "Будь ласка, переконайтеся, що у вас є робоче підключення до Інтернету!", "DialogUpdaterDirtyBuildMessage": "Ви не можете оновити брудну збірку Ryujinx!", @@ -345,10 +344,10 @@ "DialogFirmwareInstallEmbeddedSuccessMessage": "Встановлену прошивку не знайдено, але Ryujinx вдалося встановити прошивку {0} з наданої гри.\\nТепер запуститься емулятор.", "DialogFirmwareNoFirmwareInstalledMessage": "Прошивка не встановлена", "DialogFirmwareInstalledMessage": "Встановлено прошивку {0}", - "DialogInstallFileTypesSuccessMessage": "Successfully installed file types!", - "DialogInstallFileTypesErrorMessage": "Failed to install file types.", - "DialogUninstallFileTypesSuccessMessage": "Successfully uninstalled file types!", - "DialogUninstallFileTypesErrorMessage": "Failed to uninstall file types.", + "DialogInstallFileTypesSuccessMessage": "Успішно встановлено типи файлів!", + "DialogInstallFileTypesErrorMessage": "Не вдалося встановити типи файлів.", + "DialogUninstallFileTypesSuccessMessage": "Успішно видалено типи файлів!", + "DialogUninstallFileTypesErrorMessage": "Не вдалося видалити типи файлів.", "DialogOpenSettingsWindowLabel": "Відкрити вікно налаштувань", "DialogControllerAppletTitle": "Аплет контролера", "DialogMessageDialogErrorExceptionMessage": "Помилка показу діалогового вікна повідомлення: {0}", @@ -380,12 +379,15 @@ "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Версію системи {0} успішно встановлено.", "DialogUserProfileDeletionWarningMessage": "Якщо вибраний профіль буде видалено, інші профілі не відкриватимуться", "DialogUserProfileDeletionConfirmMessage": "Ви хочете видалити вибраний профіль", - "DialogUserProfileUnsavedChangesTitle": "Warning - Unsaved Changes", - "DialogUserProfileUnsavedChangesMessage": "You have made changes to this user profile that have not been saved.", - "DialogUserProfileUnsavedChangesSubMessage": "Do you want to discard your changes?", + "DialogUserProfileUnsavedChangesTitle": "Увага — Незбережені зміни", + "DialogUserProfileUnsavedChangesMessage": "Ви зробили зміни у цьому профілю користувача які не було збережено.", + "DialogUserProfileUnsavedChangesSubMessage": "Бажаєте скасувати зміни?", "DialogControllerSettingsModifiedConfirmMessage": "Поточні налаштування контролера оновлено.", "DialogControllerSettingsModifiedConfirmSubMessage": "Ви хочете зберегти?", - "DialogLoadNcaErrorMessage": "{0}. Файл з помилкою: {1}", + "DialogLoadFileErrorMessage": "{0}. Errored File: {1}", + "DialogModAlreadyExistsMessage": "Mod already exists", + "DialogModInvalidMessage": "The specified directory does not contain a mod!", + "DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!", "DialogDlcNoDlcErrorMessage": "Зазначений файл не містить DLC для вибраного заголовку!", "DialogPerformanceCheckLoggingEnabledMessage": "Ви увімкнули журнал налагодження, призначений лише для розробників.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Для оптимальної продуктивності рекомендується вимкнути ведення журналу налагодження. Ви хочете вимкнути ведення журналу налагодження зараз?", @@ -396,6 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "Зазначений файл не містить оновлення для вибраного заголовка!", "DialogSettingsBackendThreadingWarningTitle": "Попередження - потокове керування сервером", "DialogSettingsBackendThreadingWarningMessage": "Ryujinx потрібно перезапустити після зміни цього параметра, щоб він застосовувався повністю. Залежно від вашої платформи вам може знадобитися вручну вимкнути власну багатопотоковість драйвера під час використання Ryujinx.", + "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", "SettingsTabGraphicsFeaturesOptions": "Особливості", "SettingsTabGraphicsBackendMultithreading": "Багатопотоковість графічного сервера:", "CommonAuto": "Авто", @@ -430,8 +434,9 @@ "DlcManagerRemoveAllButton": "Видалити все", "DlcManagerEnableAllButton": "Увімкнути всі", "DlcManagerDisableAllButton": "Вимкнути всі", + "ModManagerDeleteAllButton": "Видалити все", "MenuBarOptionsChangeLanguage": "Змінити мову", - "MenuBarShowFileTypes": "Show File Types", + "MenuBarShowFileTypes": "Показати типи файлів", "CommonSort": "Сортувати", "CommonShowNames": "Показати назви", "CommonFavorite": "Вибрані", @@ -447,13 +452,13 @@ "CustomThemePathTooltip": "Шлях до користувацької теми графічного інтерфейсу", "CustomThemeBrowseTooltip": "Огляд користувацької теми графічного інтерфейсу", "DockModeToggleTooltip": "У режимі док-станції емульована система веде себе як приєднаний Nintendo Switch. Це покращує точність графіки в більшості ігор. І навпаки, вимкнення цього призведе до того, що емульована система поводитиметься як портативний комутатор Nintendo, погіршуючи якість графіки.\n\nНалаштуйте елементи керування для гравця 1, якщо плануєте використовувати режим док-станції; налаштуйте ручні елементи керування, якщо плануєте використовувати портативний режим.\n\nЗалиште увімкненим, якщо не впевнені.", - "DirectKeyboardTooltip": "Підтримка прямого доступу з клавіатури (HID). Надає іграм доступ до клавіатури як пристрою для введення тексту.", - "DirectMouseTooltip": "Підтримка прямого доступу миші (HID). Надає іграм доступ до миші як вказівного пристрою.", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", "RegionTooltip": "Змінити регіон системи", "LanguageTooltip": "Змінити мову системи", "TimezoneTooltip": "Змінити часовий пояс системи", "TimeTooltip": "Змінити час системи", - "VSyncToggleTooltip": "Емульована вертикальна синхронізація консолі. По суті, обмежувач кадрів для більшості ігор; його вимкнення може призвести до того, що ігри працюватимуть на вищій швидкості, екрани завантаження триватимуть довше чи зупинятимуться.\n\nМожна перемикати в грі гарячою клавішею за вашим бажанням. Ми рекомендуємо зробити це, якщо ви плануєте вимкнути його.\n\nЗалиште увімкненим, якщо не впевнені.", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", "PptcToggleTooltip": "Зберігає перекладені функції JIT, щоб їх не потрібно було перекладати кожного разу, коли гра завантажується.\n\nЗменшує заїкання та значно прискорює час завантаження після першого завантаження гри.\n\nЗалиште увімкненим, якщо не впевнені.", "FsIntegrityToggleTooltip": "Перевіряє наявність пошкоджених файлів під час завантаження гри, і якщо виявлено пошкоджені файли, показує помилку хешу в журналі.\n\nНе впливає на продуктивність і призначений для усунення несправностей.\n\nЗалиште увімкненим, якщо не впевнені.", "AudioBackendTooltip": "Змінює серверну частину, яка використовується для відтворення аудіо.\n\nSDL2 є кращим, тоді як OpenAL і SoundIO використовуються як резервні варіанти. Dummy не матиме звуку.\n\nВстановіть SDL2, якщо не впевнені.", @@ -461,16 +466,16 @@ "MemoryManagerSoftwareTooltip": "Використовує програмну таблицю сторінок для перекладу адрес. Найвища точність, але найповільніша продуктивність.", "MemoryManagerHostTooltip": "Пряме відображення пам'яті в адресному просторі хосту. Набагато швидша компіляція та виконання JIT.", "MemoryManagerUnsafeTooltip": "Пряме відображення пам’яті, але не маскує адресу в гостьовому адресному просторі перед доступом. Швидше, але ціною безпеки. Гостьова програма може отримати доступ до пам’яті з будь-якого місця в Ryujinx, тому запускайте в цьому режимі лише програми, яким ви довіряєте.", - "UseHypervisorTooltip": "Use Hypervisor instead of JIT. Greatly improves performance when available, but can be unstable in its current state.", + "UseHypervisorTooltip": "Використання гіпервізор замість JIT. Значно покращує продуктивність, коли доступний, але може бути нестабільним у поточному стані.", "DRamTooltip": "Використовує альтернативний макет MemoryMode для імітації моделі розробки Switch.\n\nЦе корисно лише для пакетів текстур з вищою роздільною здатністю або модифікацій із роздільною здатністю 4K. НЕ покращує продуктивність.\n\nЗалиште вимкненим, якщо не впевнені.", "IgnoreMissingServicesTooltip": "Ігнорує нереалізовані служби Horizon OS. Це може допомогти в обході збоїв під час завантаження певних ігор.\n\nЗалиште вимкненим, якщо не впевнені.", "GraphicsBackendThreadingTooltip": "Виконує команди графічного сервера в другому потоці.\n\nПрискорює компіляцію шейдерів, зменшує затримки та покращує продуктивність драйверів GPU без власної підтримки багатопоточності. Трохи краща продуктивність на драйверах з багатопотоковістю.\nВстановіть значення «Авто», якщо не впевнені", "GalThreadingTooltip": "Виконує команди графічного сервера в другому потоці.\n\nПрискорює компіляцію шейдерів, зменшує затримки та покращує продуктивність драйверів GPU без власної підтримки багатопоточності. Трохи краща продуктивність на драйверах з багатопотоковістю.\n\nВстановіть значення «Авто», якщо не впевнені.", "ShaderCacheToggleTooltip": "Зберігає кеш дискового шейдера, що зменшує затримки під час наступних запусків.\n\nЗалиште увімкненим, якщо не впевнені.", - "ResolutionScaleTooltip": "Масштаб роздільної здатності, застосована до відповідних цілей візуалізації", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", "ResolutionScaleEntryTooltip": "Масштаб роздільної здатності з плаваючою комою, наприклад 1,5. Не інтегральні масштаби, швидше за все, спричинять проблеми або збій.", - "AnisotropyTooltip": "Рівень анізотропної фільтрації (встановіть на «Авто», щоб використовувати значення, яке вимагає гра)", - "AspectRatioTooltip": "Співвідношення сторін, застосоване до вікна візуалізації.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", "ShaderDumpPathTooltip": "Шлях скидання графічних шейдерів", "FileLogTooltip": "Зберігає журнал консолі у файл журналу на диску. Не впливає на продуктивність.", "StubLogTooltip": "Друкує повідомлення журналу-заглушки на консолі. Не впливає на продуктивність.", @@ -504,6 +509,8 @@ "EnableInternetAccessTooltip": "Дозволяє емульованій програмі підключатися до Інтернету.\n\nІгри з режимом локальної мережі можуть підключатися одна до одної, якщо це увімкнено, і системи підключені до однієї точки доступу. Сюди входять і справжні консолі.\n\nНЕ дозволяє підключатися до серверів Nintendo. Може призвести до збою в деяких іграх, які намагаються підключитися до Інтернету.\n\nЗалиште вимкненим, якщо не впевнені.", "GameListContextMenuManageCheatToolTip": "Керування читами", "GameListContextMenuManageCheat": "Керування читами", + "GameListContextMenuManageModToolTip": "Керування модами", + "GameListContextMenuManageMod": "Керування модами", "ControllerSettingsStickRange": "Діапазон:", "DialogStopEmulationTitle": "Ryujinx - Зупинити емуляцію", "DialogStopEmulationMessage": "Ви впевнені, що хочете зупинити емуляцію?", @@ -515,8 +522,6 @@ "SettingsTabCpuMemory": "Пам'ять ЦП", "DialogUpdaterFlatpakNotSupportedMessage": "Будь ласка, оновіть Ryujinx через FlatHub.", "UpdaterDisabledWarningTitle": "Програму оновлення вимкнено!", - "GameListContextMenuOpenSdModsDirectory": "Відкрити каталог модифікацій Atmosphere", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Відкриває альтернативний каталог SD-карти Atmosphere, який містить модифікації програми. Корисно для модифікацій, упакованих для реального обладнання.", "ControllerSettingsRotate90": "Повернути на 90° за годинниковою стрілкою", "IconSize": "Розмір значка", "IconSizeTooltip": "Змінити розмір значків гри", @@ -544,12 +549,13 @@ "SwkbdMinCharacters": "Мінімальна кількість символів: {0}", "SwkbdMinRangeCharacters": "Має бути {0}-{1} символів", "SoftwareKeyboard": "Програмна клавіатура", - "SoftwareKeyboardModeNumbersOnly": "Must be numbers only", - "SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only", - "SoftwareKeyboardModeASCII": "Must be ASCII text only", - "DialogControllerAppletMessagePlayerRange": "Програма запитує {0} гравця(ів) з:\n\nТИПИ: {1}\n\nГРАВЦІ: {2}\n\n{3}Будь ласка, відкрийте «Налаштування» та повторно налаштуйте «Введення» або натисніть «Закрити».", - "DialogControllerAppletMessage": "Програма запитує рівно стільки гравців: {0} з:\n\nТИПАМИ: {1}\n\nГРАВЦІВ: {2}\n\n{3}Будь ласка, відкрийте «Налаштування» та повторно налаштуйте «Введення» або натисніть «Закрити».", - "DialogControllerAppletDockModeSet": "Встановлено режим док-станції. Ручний також недійсний.\n", + "SoftwareKeyboardModeNumeric": "Повинно бути лише 0-9 або “.”", + "SoftwareKeyboardModeAlphabet": "Повинно бути лише не CJK-символи", + "SoftwareKeyboardModeASCII": "Повинно бути лише ASCII текст", + "ControllerAppletControllers": "Підтримувані контролери:", + "ControllerAppletPlayers": "Players:", + "ControllerAppletDescription": "Your current configuration is invalid. Open settings and reconfigure your inputs.", + "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.", "UpdaterRenaming": "Перейменування старих файлів...", "UpdaterRenameFailed": "Програмі оновлення не вдалося перейменувати файл: {0}", "UpdaterAddingFiles": "Додавання нових файлів...", @@ -587,42 +593,45 @@ "Writable": "Можливість запису", "SelectDlcDialogTitle": "Виберіть файли DLC", "SelectUpdateDialogTitle": "Виберіть файли оновлення", + "SelectModDialogTitle": "Виберіть теку з модами", "UserProfileWindowTitle": "Менеджер профілів користувачів", "CheatWindowTitle": "Менеджер читів", "DlcWindowTitle": "Менеджер вмісту для завантаження", "UpdateWindowTitle": "Менеджер оновлення назв", "CheatWindowHeading": "Коди доступні для {0} [{1}]", - "BuildId": "BuildId:", + "BuildId": "ID збірки:", "DlcWindowHeading": "Вміст для завантаження, доступний для {1} ({2}): {0}", + "ModWindowHeading": "{0} мод(ів)", "UserProfilesEditProfile": "Редагувати вибране", "Cancel": "Скасувати", "Save": "Зберегти", "Discard": "Скасувати", + "Paused": "Призупинено", "UserProfilesSetProfileImage": "Встановити зображення профілю", - "UserProfileEmptyNameError": "Назва обов'язкова", - "UserProfileNoImageError": "Зображення профілю обов'язкове", + "UserProfileEmptyNameError": "Імʼя обовʼязкове", + "UserProfileNoImageError": "Зображення профілю обовʼязкове", "GameUpdateWindowHeading": "{0} Доступні оновлення для {1} ({2})", - "SettingsTabHotkeysResScaleUpHotkey": "Збільшити роздільну здатність:", - "SettingsTabHotkeysResScaleDownHotkey": "Зменшити роздільну здатність:", - "UserProfilesName": "Ім'я", + "SettingsTabHotkeysResScaleUpHotkey": "Збільшити роздільність:", + "SettingsTabHotkeysResScaleDownHotkey": "Зменшити роздільність:", + "UserProfilesName": "Імʼя", "UserProfilesUserId": "ID користувача:", "SettingsTabGraphicsBackend": "Графічний сервер", - "SettingsTabGraphicsBackendTooltip": "Графічний сервер для використання", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", "SettingsEnableTextureRecompression": "Увімкнути рекомпресію текстури", - "SettingsEnableTextureRecompressionTooltip": "Стискає певні текстури, щоб зменшити використання VRAM.\n\nРекомендовано для використання з графічними процесорами, які мають менш ніж 4 ГБ відеопам’яті.\n\nЗалиште вимкненим, якщо не впевнені.", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", "SettingsTabGraphicsPreferredGpu": "Бажаний GPU", "SettingsTabGraphicsPreferredGpuTooltip": "Виберіть відеокарту, яка використовуватиметься з графічним сервером Vulkan.\n\nНе впливає на графічний процесор, який використовуватиме OpenGL.\n\nЯкщо не впевнені, встановіть графічний процесор, позначений як «dGPU». Якщо такого немає, залиште це.", "SettingsAppRequiredRestartMessage": "Необхідно перезапустити Ryujinx", "SettingsGpuBackendRestartMessage": "Налаштування графічного сервера або GPU було змінено. Для цього знадобиться перезапуск", - "SettingsGpuBackendRestartSubMessage": "Ви хочете перезапустити зараз?", - "RyujinxUpdaterMessage": "Хочете оновити Ryujinx до останньої версії?", + "SettingsGpuBackendRestartSubMessage": "Бажаєте перезапустити зараз?", + "RyujinxUpdaterMessage": "Бажаєте оновити Ryujinx до останньої версії?", "SettingsTabHotkeysVolumeUpHotkey": "Збільшити гучність:", "SettingsTabHotkeysVolumeDownHotkey": "Зменшити гучність:", "SettingsEnableMacroHLE": "Увімкнути макрос HLE", "SettingsEnableMacroHLETooltip": "Високорівнева емуляція коду макросу GPU.\n\nПокращує продуктивність, але може викликати графічні збої в деяких іграх.\n\nЗалиште увімкненим, якщо не впевнені.", - "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", - "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", - "VolumeShort": "Гуч", + "SettingsEnableColorSpacePassthrough": "Наскрізний колірний простір", + "SettingsEnableColorSpacePassthroughTooltip": "Дозволяє серверу Vulkan передавати інформацію про колір без вказівки колірного простору. Для користувачів з екранами з широкою гамою це може призвести до більш яскравих кольорів, але шляхом втрати коректності передачі кольору.", + "VolumeShort": "Гуч.", "UserProfilesManageSaves": "Керувати збереженнями", "DeleteUserSave": "Ви хочете видалити збереження користувача для цієї гри?", "IrreversibleActionNote": "Цю дію не можна скасувати.", @@ -634,23 +643,26 @@ "UserProfilesRecoverLostAccounts": "Відновлення втрачених облікових записів", "Recover": "Відновити", "UserProfilesRecoverHeading": "Знайдено збереження для наступних облікових записів", - "UserProfilesRecoverEmptyList": "No profiles to recover", - "GraphicsAATooltip": "Applies anti-aliasing to the game render", - "GraphicsAALabel": "Anti-Aliasing:", - "GraphicsScalingFilterLabel": "Scaling Filter:", - "GraphicsScalingFilterTooltip": "Enables Framebuffer Scaling", - "GraphicsScalingFilterLevelLabel": "Level", - "GraphicsScalingFilterLevelTooltip": "Set Scaling Filter Level", - "SmaaLow": "SMAA Low", - "SmaaMedium": "SMAA Medium", - "SmaaHigh": "SMAA High", - "SmaaUltra": "SMAA Ultra", - "UserEditorTitle": "Edit User", - "UserEditorTitleCreate": "Create User", - "SettingsTabNetworkInterface": "Network Interface:", - "NetworkInterfaceTooltip": "The network interface used for LAN features", - "NetworkInterfaceDefault": "Default", - "PackagingShaders": "Packaging Shaders", - "AboutChangelogButton": "View Changelog on GitHub", - "AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser." -} \ No newline at end of file + "UserProfilesRecoverEmptyList": "Немає профілів для відновлення", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", + "GraphicsAALabel": "Згладжування:", + "GraphicsScalingFilterLabel": "Фільтр масштабування:", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterLevelLabel": "Рівень", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", + "SmaaLow": "SMAA Низький", + "SmaaMedium": "SMAA Середній", + "SmaaHigh": "SMAA Високий", + "SmaaUltra": "SMAA Ультра", + "UserEditorTitle": "Редагувати користувача", + "UserEditorTitleCreate": "Створити користувача", + "SettingsTabNetworkInterface": "Мережевий інтерфейс:", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", + "NetworkInterfaceDefault": "Стандартний", + "PackagingShaders": "Пакування шейдерів", + "AboutChangelogButton": "Переглянути журнал змін на GitHub", + "AboutChangelogButtonTooltipMessage": "Клацніть, щоб відкрити журнал змін для цієї версії у стандартному браузері.", + "SettingsTabNetworkMultiplayer": "Мережева гра", + "MultiplayerMode": "Режим:", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." +} diff --git a/src/Ryujinx/Assets/Locales/zh_CN.json b/src/Ryujinx/Assets/Locales/zh_CN.json index d09a80ec6e..ce07385c1f 100644 --- a/src/Ryujinx/Assets/Locales/zh_CN.json +++ b/src/Ryujinx/Assets/Locales/zh_CN.json @@ -4,14 +4,14 @@ "MenuBarFileOpenAppletOpenMiiAppletToolTip": "打开独立的 Mii 小程序", "SettingsTabInputDirectMouseAccess": "直通鼠标操作", "SettingsTabSystemMemoryManagerMode": "内存管理模式:", - "SettingsTabSystemMemoryManagerModeSoftware": "软件", - "SettingsTabSystemMemoryManagerModeHost": "本机 (快速)", - "SettingsTabSystemMemoryManagerModeHostUnchecked": "跳过检查的本机 (最快)", + "SettingsTabSystemMemoryManagerModeSoftware": "软件管理", + "SettingsTabSystemMemoryManagerModeHost": "本机映射 (较快)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "跳过检查的本机映射 (最快,但不安全)", "SettingsTabSystemUseHypervisor": "使用 Hypervisor 虚拟化", "MenuBarFile": "文件", - "MenuBarFileOpenFromFile": "加载文件", + "MenuBarFileOpenFromFile": "加载游戏文件", "MenuBarFileOpenUnpacked": "加载解包后的游戏", - "MenuBarFileOpenEmuFolder": "打开 Ryujinx 文件夹", + "MenuBarFileOpenEmuFolder": "打开 Ryujinx 系统文件夹", "MenuBarFileOpenLogsFolder": "打开日志文件夹", "MenuBarFileExit": "退出", "MenuBarOptions": "选项", @@ -51,14 +51,12 @@ "GameListContextMenuOpenBcatSaveDirectory": "打开 BCAT 目录", "GameListContextMenuOpenBcatSaveDirectoryToolTip": "打开包含游戏 BCAT 数据的目录", "GameListContextMenuManageTitleUpdates": "管理游戏更新", - "GameListContextMenuManageTitleUpdatesToolTip": "打开更新管理器", + "GameListContextMenuManageTitleUpdatesToolTip": "打开游戏更新管理窗口", "GameListContextMenuManageDlc": "管理 DLC", "GameListContextMenuManageDlcToolTip": "打开 DLC 管理窗口", - "GameListContextMenuOpenModsDirectory": "打开 MOD 目录", - "GameListContextMenuOpenModsDirectoryToolTip": "打开存放游戏 MOD 的目录", "GameListContextMenuCacheManagement": "缓存管理", "GameListContextMenuCacheManagementPurgePptc": "清除已编译的 PPTC 文件", - "GameListContextMenuCacheManagementPurgePptcToolTip": "仅删除 PPTC 转换后的文件,下次打开游戏时将根据 .info 文件重新生成 PPTC 文件。\n如想彻底清除缓存,请进入目录把 .info 文件一并删除", + "GameListContextMenuCacheManagementPurgePptcToolTip": "仅删除 PPTC 转换后的文件,下次打开游戏时将根据 .info 文件重新生成 PPTC 文件。\n如想彻底清除缓存,请进入CPU目录把 .info 文件一并删除", "GameListContextMenuCacheManagementPurgeShaderCache": "清除着色器缓存", "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "删除游戏的着色器缓存", "GameListContextMenuCacheManagementOpenPptcDirectory": "打开 PPTC 目录", @@ -72,25 +70,32 @@ "GameListContextMenuExtractDataRomFSToolTip": "从游戏的当前状态中提取 RomFS 分区 (包括更新)", "GameListContextMenuExtractDataLogo": "图标", "GameListContextMenuExtractDataLogoToolTip": "从游戏的当前状态中提取图标 (包括更新)", + "GameListContextMenuCreateShortcut": "创建游戏快捷方式", + "GameListContextMenuCreateShortcutToolTip": "创建一个直接启动此游戏的桌面快捷方式", + "GameListContextMenuCreateShortcutToolTipMacOS": "在 macOS 的应用程序文件夹中创建一个直接启动此游戏的快捷方式", + "GameListContextMenuOpenModsDirectory": "打开 MOD 目录", + "GameListContextMenuOpenModsDirectoryToolTip": "打开存放游戏 MOD 的目录", + "GameListContextMenuOpenSdModsDirectory": "打开大气层系统 MOD 目录", + "GameListContextMenuOpenSdModsDirectoryToolTip": "打开适用于大气层系统的游戏 MOD 目录,对于为真实硬件打包的 MOD 非常有用", "StatusBarGamesLoaded": "{0}/{1} 游戏加载完成", "StatusBarSystemVersion": "系统版本:{0}", - "LinuxVmMaxMapCountDialogTitle": "检测到内存映射的限制过低", - "LinuxVmMaxMapCountDialogTextPrimary": "你想要将 vm.max_map_count 的值增加到 {0} 吗", - "LinuxVmMaxMapCountDialogTextSecondary": "有些游戏可能会试图创建超过当前允许的内存映射数量。当超过此限制时,Ryujinx会立即崩溃。", - "LinuxVmMaxMapCountDialogButtonUntilRestart": "确定,关闭后重置", + "LinuxVmMaxMapCountDialogTitle": "检测到操作系统内存映射最大数量被设置的过低", + "LinuxVmMaxMapCountDialogTextPrimary": "你想要将操作系统 vm.max_map_count 的值增加到 {0} 吗", + "LinuxVmMaxMapCountDialogTextSecondary": "有些游戏可能会尝试创建超过当前系统允许的内存映射最大数量,若超过当前最大数量,Ryujinx 模拟器将会闪退。", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "确定,临时保存(重启后失效)", "LinuxVmMaxMapCountDialogButtonPersistent": "确定,永久保存", "LinuxVmMaxMapCountWarningTextPrimary": "内存映射的最大数量低于推荐值。", - "LinuxVmMaxMapCountWarningTextSecondary": "vm.max_map_count ({0}) 的当前值小于 {1}。 有些游戏可能会试图创建超过当前允许的内存映射量。当大于此限制时,Ryujinx 会立即崩溃。\n\n你可以手动增加内存映射限制或者安装 pkexec,它可以辅助Ryujinx解决该问题。", + "LinuxVmMaxMapCountWarningTextSecondary": "vm.max_map_count ({0}) 的当前值小于 {1}。 有些游戏可能会尝试创建超过当前系统允许的内存映射最大数量,若超过当前最大数量,Ryujinx 模拟器将会闪退。\n\n你可以手动增加内存映射最大数量,或者安装 pkexec,它可以辅助 Ryujinx 完成内存映射最大数量的修改操作。", "Settings": "设置", "SettingsTabGeneral": "用户界面", "SettingsTabGeneralGeneral": "常规", "SettingsTabGeneralEnableDiscordRichPresence": "启用 Discord 在线状态展示", - "SettingsTabGeneralCheckUpdatesOnLaunch": "自动检查更新", - "SettingsTabGeneralShowConfirmExitDialog": "显示 \"确认退出\" 对话框", + "SettingsTabGeneralCheckUpdatesOnLaunch": "启动时检查更新", + "SettingsTabGeneralShowConfirmExitDialog": "退出游戏时需要确认", "SettingsTabGeneralHideCursor": "隐藏鼠标指针:", - "SettingsTabGeneralHideCursorNever": "从不", + "SettingsTabGeneralHideCursorNever": "从不隐藏", "SettingsTabGeneralHideCursorOnIdle": "自动隐藏", - "SettingsTabGeneralHideCursorAlways": "始终", + "SettingsTabGeneralHideCursorAlways": "始终隐藏", "SettingsTabGeneralGameDirectories": "游戏目录", "SettingsTabGeneralAdd": "添加", "SettingsTabGeneralRemove": "删除", @@ -106,18 +111,18 @@ "SettingsTabSystemSystemRegionTaiwan": "台湾地区", "SettingsTabSystemSystemLanguage": "系统语言:", "SettingsTabSystemSystemLanguageJapanese": "日语", - "SettingsTabSystemSystemLanguageAmericanEnglish": "美式英语", + "SettingsTabSystemSystemLanguageAmericanEnglish": "英语(美国)", "SettingsTabSystemSystemLanguageFrench": "法语", "SettingsTabSystemSystemLanguageGerman": "德语", "SettingsTabSystemSystemLanguageItalian": "意大利语", "SettingsTabSystemSystemLanguageSpanish": "西班牙语", - "SettingsTabSystemSystemLanguageChinese": "简体中文", + "SettingsTabSystemSystemLanguageChinese": "中文(简体)——无效", "SettingsTabSystemSystemLanguageKorean": "韩语", "SettingsTabSystemSystemLanguageDutch": "荷兰语", "SettingsTabSystemSystemLanguagePortuguese": "葡萄牙语", "SettingsTabSystemSystemLanguageRussian": "俄语", - "SettingsTabSystemSystemLanguageTaiwanese": "繁体中文(台湾)", - "SettingsTabSystemSystemLanguageBritishEnglish": "英式英语", + "SettingsTabSystemSystemLanguageTaiwanese": "中文(繁体)——无效", + "SettingsTabSystemSystemLanguageBritishEnglish": "英语(英国)", "SettingsTabSystemSystemLanguageCanadianFrench": "加拿大法语", "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "拉美西班牙语", "SettingsTabSystemSystemLanguageSimplifiedChinese": "简体中文(推荐)", @@ -126,15 +131,15 @@ "SettingsTabSystemSystemTime": "系统时钟:", "SettingsTabSystemEnableVsync": "启用垂直同步", "SettingsTabSystemEnablePptc": "开启 PPTC 缓存", - "SettingsTabSystemEnableFsIntegrityChecks": "文件系统完整性检查", - "SettingsTabSystemAudioBackend": "音频后端:", + "SettingsTabSystemEnableFsIntegrityChecks": "启用文件系统完整性检查", + "SettingsTabSystemAudioBackend": "音频处理引擎:", "SettingsTabSystemAudioBackendDummy": "无", "SettingsTabSystemAudioBackendOpenAL": "OpenAL", - "SettingsTabSystemAudioBackendSoundIO": "音频输入/输出", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSDL2": "SDL2", - "SettingsTabSystemHacks": "修正", - "SettingsTabSystemHacksNote": "(会引起模拟器不稳定)", - "SettingsTabSystemExpandDramSize": "使用开发机的内存布局", + "SettingsTabSystemHacks": "修改", + "SettingsTabSystemHacksNote": "会导致模拟器不稳定", + "SettingsTabSystemExpandDramSize": "使用开发机的内存布局(开发人员使用)", "SettingsTabSystemIgnoreMissingServices": "忽略缺失的服务", "SettingsTabGraphics": "图形", "SettingsTabGraphicsAPI": "图形 API", @@ -148,9 +153,9 @@ "SettingsTabGraphicsResolutionScale": "分辨率缩放:", "SettingsTabGraphicsResolutionScaleCustom": "自定义(不推荐)", "SettingsTabGraphicsResolutionScaleNative": "原生 (720p/1080p)", - "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", - "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsResolutionScale2x": "2 倍 (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3 倍 (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4 倍 (2880p/4320p) (不推荐)", "SettingsTabGraphicsAspectRatio": "宽高比:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -162,18 +167,18 @@ "SettingsTabGraphicsShaderDumpPath": "图形着色器转储路径:", "SettingsTabLogging": "日志", "SettingsTabLoggingLogging": "日志", - "SettingsTabLoggingEnableLoggingToFile": "保存日志为文件", - "SettingsTabLoggingEnableStubLogs": "启用 Stub 日志", + "SettingsTabLoggingEnableLoggingToFile": "将日志写入文件", + "SettingsTabLoggingEnableStubLogs": "启用存根日志", "SettingsTabLoggingEnableInfoLogs": "启用信息日志", "SettingsTabLoggingEnableWarningLogs": "启用警告日志", "SettingsTabLoggingEnableErrorLogs": "启用错误日志", "SettingsTabLoggingEnableTraceLogs": "启用跟踪日志", - "SettingsTabLoggingEnableGuestLogs": "启用来宾日志", - "SettingsTabLoggingEnableFsAccessLogs": "启用访问日志", - "SettingsTabLoggingFsGlobalAccessLogMode": "全局访问日志模式:", + "SettingsTabLoggingEnableGuestLogs": "启用访客日志", + "SettingsTabLoggingEnableFsAccessLogs": "启用文件访问日志", + "SettingsTabLoggingFsGlobalAccessLogMode": "文件系统全局访问日志模式:", "SettingsTabLoggingDeveloperOptions": "开发者选项", - "SettingsTabLoggingDeveloperOptionsNote": "警告:会降低性能", - "SettingsTabLoggingGraphicsBackendLogLevel": "图形后端日志级别:", + "SettingsTabLoggingDeveloperOptionsNote": "警告:会降低模拟器性能", + "SettingsTabLoggingGraphicsBackendLogLevel": "图形引擎日志级别:", "SettingsTabLoggingGraphicsBackendLogLevelNone": "无", "SettingsTabLoggingGraphicsBackendLogLevelError": "错误", "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "减速", @@ -183,7 +188,7 @@ "SettingsTabInputEnableDockedMode": "主机模式", "SettingsTabInputDirectKeyboardAccess": "直通键盘控制", "SettingsButtonSave": "保存", - "SettingsButtonClose": "取消", + "SettingsButtonClose": "关闭", "SettingsButtonOk": "确定", "SettingsButtonCancel": "取消", "SettingsButtonApply": "应用", @@ -199,19 +204,19 @@ "ControllerSettingsHandheld": "掌机模式", "ControllerSettingsInputDevice": "输入设备", "ControllerSettingsRefresh": "刷新", - "ControllerSettingsDeviceDisabled": "关闭", + "ControllerSettingsDeviceDisabled": "禁用", "ControllerSettingsControllerType": "手柄类型", "ControllerSettingsControllerTypeHandheld": "掌机", "ControllerSettingsControllerTypeProController": "Pro 手柄", - "ControllerSettingsControllerTypeJoyConPair": "JoyCon 组合", - "ControllerSettingsControllerTypeJoyConLeft": "左 JoyCon", - "ControllerSettingsControllerTypeJoyConRight": "右 JoyCon", - "ControllerSettingsProfile": "预设", - "ControllerSettingsProfileDefault": "默认布局", + "ControllerSettingsControllerTypeJoyConPair": "双 JoyCon 手柄", + "ControllerSettingsControllerTypeJoyConLeft": "左 JoyCon 手柄", + "ControllerSettingsControllerTypeJoyConRight": "右 JoyCon 手柄", + "ControllerSettingsProfile": "配置文件", + "ControllerSettingsProfileDefault": "默认设置", "ControllerSettingsLoad": "加载", "ControllerSettingsAdd": "新建", "ControllerSettingsRemove": "删除", - "ControllerSettingsButtons": "按键", + "ControllerSettingsButtons": "基础按键", "ControllerSettingsButtonA": "A", "ControllerSettingsButtonB": "B", "ControllerSettingsButtonX": "X", @@ -229,8 +234,8 @@ "ControllerSettingsStickLeft": "左", "ControllerSettingsStickRight": "右", "ControllerSettingsStickStick": "摇杆", - "ControllerSettingsStickInvertXAxis": "反转 X 轴方向", - "ControllerSettingsStickInvertYAxis": "反转 Y 轴方向", + "ControllerSettingsStickInvertXAxis": "摇杆左右反转", + "ControllerSettingsStickInvertYAxis": "摇杆上下反转", "ControllerSettingsStickDeadzone": "死区:", "ControllerSettingsLStick": "左摇杆", "ControllerSettingsRStick": "右摇杆", @@ -252,28 +257,28 @@ "ControllerSettingsMisc": "其他", "ControllerSettingsTriggerThreshold": "扳机阈值:", "ControllerSettingsMotion": "体感", - "ControllerSettingsMotionUseCemuhookCompatibleMotion": "使用 CemuHook 体感协议", - "ControllerSettingsMotionControllerSlot": "手柄:", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "使用 CemuHook 兼容的体感协议", + "ControllerSettingsMotionControllerSlot": "手柄槽位:", "ControllerSettingsMotionMirrorInput": "镜像操作", - "ControllerSettingsMotionRightJoyConSlot": "右JoyCon:", - "ControllerSettingsMotionServerHost": "服务器Host:", + "ControllerSettingsMotionRightJoyConSlot": "右 JoyCon 槽位:", + "ControllerSettingsMotionServerHost": "服务器地址:", "ControllerSettingsMotionGyroSensitivity": "陀螺仪敏感度:", "ControllerSettingsMotionGyroDeadzone": "陀螺仪死区:", "ControllerSettingsSave": "保存", "ControllerSettingsClose": "关闭", - "UserProfilesSelectedUserProfile": "选择的用户账户:", + "UserProfilesSelectedUserProfile": "选定的用户账户:", "UserProfilesSaveProfileName": "保存名称", "UserProfilesChangeProfileImage": "更换头像", - "UserProfilesAvailableUserProfiles": "现有账户:", + "UserProfilesAvailableUserProfiles": "现有用户账户:", "UserProfilesAddNewProfile": "新建账户", "UserProfilesDelete": "删除", "UserProfilesClose": "关闭", - "ProfileNameSelectionWatermark": "选择昵称", + "ProfileNameSelectionWatermark": "输入昵称", "ProfileImageSelectionTitle": "选择头像", "ProfileImageSelectionHeader": "选择合适的头像图片", - "ProfileImageSelectionNote": "您可以导入自定义头像,或从系统中选择头像", + "ProfileImageSelectionNote": "您可以导入自定义头像,或从模拟器系统中选择预设头像", "ProfileImageSelectionImportImage": "导入图像文件", - "ProfileImageSelectionSelectAvatar": "选择系统头像", + "ProfileImageSelectionSelectAvatar": "选择预设头像", "InputDialogTitle": "输入对话框", "InputDialogOk": "完成", "InputDialogCancel": "取消", @@ -283,121 +288,120 @@ "AvatarChoose": "选择头像", "AvatarSetBackgroundColor": "设置背景色", "AvatarClose": "关闭", - "ControllerSettingsLoadProfileToolTip": "加载预设", - "ControllerSettingsAddProfileToolTip": "新增预设", - "ControllerSettingsRemoveProfileToolTip": "删除预设", - "ControllerSettingsSaveProfileToolTip": "保存预设", - "MenuBarFileToolsTakeScreenshot": "保存截图", - "MenuBarFileToolsHideUi": "隐藏界面", - "GameListContextMenuRunApplication": "运行应用", + "ControllerSettingsLoadProfileToolTip": "加载配置文件", + "ControllerSettingsAddProfileToolTip": "新增配置文件", + "ControllerSettingsRemoveProfileToolTip": "删除配置文件", + "ControllerSettingsSaveProfileToolTip": "保存配置文件", + "MenuBarFileToolsTakeScreenshot": "保存截屏", + "MenuBarFileToolsHideUi": "隐藏菜单栏和状态栏", + "GameListContextMenuRunApplication": "启动游戏", "GameListContextMenuToggleFavorite": "收藏", - "GameListContextMenuToggleFavoriteToolTip": "标记喜爱的游戏", - "SettingsTabGeneralTheme": "主题", - "SettingsTabGeneralThemeCustomTheme": "自选主题路径", - "SettingsTabGeneralThemeBaseStyle": "主题色调", - "SettingsTabGeneralThemeBaseStyleDark": "暗黑", - "SettingsTabGeneralThemeBaseStyleLight": "浅色", - "SettingsTabGeneralThemeEnableCustomTheme": "使用自选主题界面", - "ButtonBrowse": "浏览", + "GameListContextMenuToggleFavoriteToolTip": "切换游戏的收藏状态", + "SettingsTabGeneralTheme": "主题︰", + "SettingsTabGeneralThemeDark": "深色(暗黑)", + "SettingsTabGeneralThemeLight": "浅色(亮色)", "ControllerSettingsConfigureGeneral": "配置", "ControllerSettingsRumble": "震动", "ControllerSettingsRumbleStrongMultiplier": "强震动幅度", "ControllerSettingsRumbleWeakMultiplier": "弱震动幅度", "DialogMessageSaveNotAvailableMessage": "没有{0} [{1:x16}]的游戏存档", "DialogMessageSaveNotAvailableCreateSaveMessage": "是否创建该游戏的存档文件夹?", - "DialogConfirmationTitle": "Ryujinx - 设置", + "DialogConfirmationTitle": "Ryujinx - 确认", "DialogUpdaterTitle": "Ryujinx - 更新", "DialogErrorTitle": "Ryujinx - 错误", "DialogWarningTitle": "Ryujinx - 警告", - "DialogExitTitle": "Ryujinx - 关闭", - "DialogErrorMessage": "Ryujinx 发生错误", - "DialogExitMessage": "是否关闭 Ryujinx?", + "DialogExitTitle": "Ryujinx - 退出", + "DialogErrorMessage": "Ryujinx 模拟器发生错误", + "DialogExitMessage": "是否关闭 Ryujinx 模拟器?", "DialogExitSubMessage": "未保存的进度将会丢失!", - "DialogMessageCreateSaveErrorMessage": "创建特定的存档时出错:{0}", - "DialogMessageFindSaveErrorMessage": "查找特定的存档时出错:{0}", - "FolderDialogExtractTitle": "选择要解压到的文件夹", + "DialogMessageCreateSaveErrorMessage": "创建指定存档时出错:{0}", + "DialogMessageFindSaveErrorMessage": "查找指定存档时出错:{0}", + "FolderDialogExtractTitle": "选择要提取到的文件夹", "DialogNcaExtractionMessage": "提取 {1} 的 {0} 分区...", - "DialogNcaExtractionTitle": "Ryujinx - NCA分区提取", - "DialogNcaExtractionMainNcaNotFoundErrorMessage": "提取失败。所选文件中不含主NCA文件", - "DialogNcaExtractionCheckLogErrorMessage": "提取失败。请查看日志文件获取详情。", - "DialogNcaExtractionSuccessMessage": "提取成功。", - "DialogUpdaterConvertFailedMessage": "无法转换当前 Ryujinx 版本。", - "DialogUpdaterCancelUpdateMessage": "更新取消!", - "DialogUpdaterAlreadyOnLatestVersionMessage": "您使用的 Ryujinx 是最新版本。", - "DialogUpdaterFailedToGetVersionMessage": "尝试从 Github 获取版本信息时无效。\n可能由于 GitHub Actions 正在编译新版本。请过一会再试。", - "DialogUpdaterConvertFailedGithubMessage": "无法转换从 Github 接收到的 Ryujinx 版本。", - "DialogUpdaterDownloadingMessage": "下载新版本中...", + "DialogNcaExtractionTitle": "Ryujinx - NCA 分区提取", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "提取失败,所选文件中没有 NCA 文件", + "DialogNcaExtractionCheckLogErrorMessage": "提取失败,请查看日志文件获取详情", + "DialogNcaExtractionSuccessMessage": "提取成功!", + "DialogUpdaterConvertFailedMessage": "无法切换当前 Ryujinx 版本。", + "DialogUpdaterCancelUpdateMessage": "取消更新!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "您使用的 Ryujinx 模拟器是最新版本。", + "DialogUpdaterFailedToGetVersionMessage": "尝试从 Github 获取版本信息时无效,可能由于 GitHub Actions 正在编译新版本。\n请过一会再试。", + "DialogUpdaterConvertFailedGithubMessage": "无法切换至从 Github 接收到的新版 Ryujinx 模拟器。", + "DialogUpdaterDownloadingMessage": "下载更新中...", "DialogUpdaterExtractionMessage": "正在提取更新...", - "DialogUpdaterRenamingMessage": "正在删除旧文件...", + "DialogUpdaterRenamingMessage": "正在重命名更新...", "DialogUpdaterAddingFilesMessage": "安装更新中...", "DialogUpdaterCompleteMessage": "更新成功!", - "DialogUpdaterRestartMessage": "立即重启 Ryujinx 完成更新?", - "DialogUpdaterArchNotSupportedMessage": "您运行的系统架构不受支持!", - "DialogUpdaterArchNotSupportedSubMessage": "(仅支持 x64 系统)", - "DialogUpdaterNoInternetMessage": "没有连接到互联网", + "DialogUpdaterRestartMessage": "是否立即重启 Ryujinx 模拟器?", + "DialogUpdaterNoInternetMessage": "没有连接到网络", "DialogUpdaterNoInternetSubMessage": "请确保互联网连接正常。", - "DialogUpdaterDirtyBuildMessage": "不能更新非官方版本的 Ryujinx!", - "DialogUpdaterDirtyBuildSubMessage": "如果希望使用受支持的版本,请您在 https://ryujinx.org/ 下载。", + "DialogUpdaterDirtyBuildMessage": "无法更新非官方版本的 Ryujinx 模拟器!", + "DialogUpdaterDirtyBuildSubMessage": "如果想使用受支持的版本,请您在 https://ryujinx.org/ 下载官方版本。", "DialogRestartRequiredMessage": "需要重启模拟器", - "DialogThemeRestartMessage": "主题设置已保存。需要重新启动才能生效。", - "DialogThemeRestartSubMessage": "您是否要重启?", - "DialogFirmwareInstallEmbeddedMessage": "要安装游戏内置的固件吗?(固件 {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "未找到已安装的固件,但 Ryujinx 可以从现有的游戏安装固件{0}.\n模拟器现在可以运行。", + "DialogThemeRestartMessage": "主题设置已保存,需要重启模拟器才能生效。", + "DialogThemeRestartSubMessage": "是否要重启模拟器?", + "DialogFirmwareInstallEmbeddedMessage": "要安装游戏内置的系统固件吗?(固件 {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "未找到已安装的固件,Ryujinx 已经从当前游戏中安装了系统固件 {0} 。\n模拟器现在可以运行。", "DialogFirmwareNoFirmwareInstalledMessage": "未安装固件", "DialogFirmwareInstalledMessage": "已安装固件 {0}", "DialogInstallFileTypesSuccessMessage": "关联文件类型成功!", - "DialogInstallFileTypesErrorMessage": "关联文件类型失败。", + "DialogInstallFileTypesErrorMessage": "关联文件类型失败!", "DialogUninstallFileTypesSuccessMessage": "成功解除文件类型关联!", - "DialogUninstallFileTypesErrorMessage": "解除文件类型关联失败。", + "DialogUninstallFileTypesErrorMessage": "解除文件类型关联失败!", "DialogOpenSettingsWindowLabel": "打开设置窗口", "DialogControllerAppletTitle": "控制器小窗口", "DialogMessageDialogErrorExceptionMessage": "显示消息对话框时出错:{0}", "DialogSoftwareKeyboardErrorExceptionMessage": "显示软件键盘时出错:{0}", "DialogErrorAppletErrorExceptionMessage": "显示错误对话框时出错:{0}", "DialogUserErrorDialogMessage": "{0}: {1}", - "DialogUserErrorDialogInfoMessage": "\n有关修复此错误的更多信息,可以遵循我们的设置指南。", + "DialogUserErrorDialogInfoMessage": "\n有关修复此错误的更多信息,可以遵循我们的安装指南。", "DialogUserErrorDialogTitle": "Ryujinx 错误 ({0})", "DialogAmiiboApiTitle": "Amiibo API", "DialogAmiiboApiFailFetchMessage": "从 API 获取信息时出错。", - "DialogAmiiboApiConnectErrorMessage": "无法连接到 Amiibo API 服务器。服务器可能已关闭,或者您没有连接网络。", - "DialogProfileInvalidProfileErrorMessage": "预设 {0} 与当前输入配置系统不兼容。", - "DialogProfileDefaultProfileOverwriteErrorMessage": "默认预设不能被覆盖", - "DialogProfileDeleteProfileTitle": "删除预设", + "DialogAmiiboApiConnectErrorMessage": "无法连接到 Amiibo API 服务器,服务可能已关闭,或者没有连接互联网。", + "DialogProfileInvalidProfileErrorMessage": "配置文件 {0} 与当前输入配置系统不兼容。", + "DialogProfileDefaultProfileOverwriteErrorMessage": "不允许覆盖默认配置文件", + "DialogProfileDeleteProfileTitle": "删除配置文件", "DialogProfileDeleteProfileMessage": "删除后不可恢复,确认删除吗?", "DialogWarning": "警告", "DialogPPTCDeletionMessage": "您即将删除:\n\n{0} 的 PPTC 缓存\n\n确定吗?", - "DialogPPTCDeletionErrorMessage": "清除位于 {0} 的 PPTC 缓存时出错:{1}", + "DialogPPTCDeletionErrorMessage": "清除 {0} 的 PPTC 缓存时出错:{1}", "DialogShaderDeletionMessage": "您即将删除:\n\n{0} 的着色器缓存\n\n确定吗?", - "DialogShaderDeletionErrorMessage": "清除位于 {0} 的着色器缓存时出错:{1}", - "DialogRyujinxErrorMessage": "Ryujinx 遇到错误", - "DialogInvalidTitleIdErrorMessage": "UI错误:所选游戏没有有效的标题ID", - "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "路径 {0} 找不到有效的系统固件。", - "DialogFirmwareInstallerFirmwareInstallTitle": "固件 {0}", + "DialogShaderDeletionErrorMessage": "清除 {0} 的着色器缓存时出错:{1}", + "DialogRyujinxErrorMessage": "Ryujinx 模拟器发生错误", + "DialogInvalidTitleIdErrorMessage": "用户界面错误:所选游戏没有有效的游戏 ID", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "在路径 {0} 中找不到有效的系统固件。", + "DialogFirmwareInstallerFirmwareInstallTitle": "安装固件 {0}", "DialogFirmwareInstallerFirmwareInstallMessage": "即将安装系统版本 {0} 。", - "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n会替换当前系统版本 {0} 。", - "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n是否确认继续?", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n替换当前系统版本 {0} 。", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n是否继续?", "DialogFirmwareInstallerFirmwareInstallWaitMessage": "安装固件中...", "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "成功安装系统版本 {0} 。", - "DialogUserProfileDeletionWarningMessage": "删除后将没有可选择的用户账户", - "DialogUserProfileDeletionConfirmMessage": "是否删除选择的账户", - "DialogUserProfileUnsavedChangesTitle": "警告 - 未保存的更改", - "DialogUserProfileUnsavedChangesMessage": "您为该用户做出的部分改动尚未保存。", - "DialogUserProfileUnsavedChangesSubMessage": "是否舍弃这些改动?", - "DialogControllerSettingsModifiedConfirmMessage": "目前的输入预设已更新", - "DialogControllerSettingsModifiedConfirmSubMessage": "要保存吗?", - "DialogLoadNcaErrorMessage": "{0}. 错误的文件:{1}", - "DialogDlcNoDlcErrorMessage": "选择的文件不包含所选游戏的 DLC!", - "DialogPerformanceCheckLoggingEnabledMessage": "您启用了跟踪日志,仅供开发人员使用。", + "DialogUserProfileDeletionWarningMessage": "删除后将没有可用的账户", + "DialogUserProfileDeletionConfirmMessage": "是否删除所选账户", + "DialogUserProfileUnsavedChangesTitle": "警告 - 有未保存的更改", + "DialogUserProfileUnsavedChangesMessage": "您对该账户的更改尚未保存。", + "DialogUserProfileUnsavedChangesSubMessage": "确定要放弃更改吗?", + "DialogControllerSettingsModifiedConfirmMessage": "当前的输入设置已更新", + "DialogControllerSettingsModifiedConfirmSubMessage": "是否保存?", + "DialogLoadFileErrorMessage": "{0}. 错误的文件:{1}", + "DialogModAlreadyExistsMessage": "MOD 已存在", + "DialogModInvalidMessage": "指定的目录找不到 MOD!", + "DialogModDeleteNoParentMessage": "删除失败:找不到 MOD 的父目录“{0}”!", + "DialogDlcNoDlcErrorMessage": "选择的文件不是当前游戏的 DLC!", + "DialogPerformanceCheckLoggingEnabledMessage": "您启用了跟踪日志,该功能仅供开发人员使用。", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "为了获得最佳性能,建议禁用跟踪日志记录。您是否要立即禁用?", - "DialogPerformanceCheckShaderDumpEnabledMessage": "您启用了着色器转储,仅供开发人员使用。", + "DialogPerformanceCheckShaderDumpEnabledMessage": "您启用了着色器转储,该功能仅供开发人员使用。", "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "为了获得最佳性能,建议禁用着色器转储。您是否要立即禁用?", - "DialogLoadAppGameAlreadyLoadedMessage": "已有游戏正在运行", - "DialogLoadAppGameAlreadyLoadedSubMessage": "请停止模拟或关闭程序,再启动另一个游戏。", - "DialogUpdateAddUpdateErrorMessage": "选择的文件不包含所选游戏的更新!", - "DialogSettingsBackendThreadingWarningTitle": "警告 - 后端多线程", - "DialogSettingsBackendThreadingWarningMessage": "改变此选项后必须重启 Ryujinx 才能生效。\n\n取决于您的硬件,可能需要手动禁用驱动面板中的线程优化。", + "DialogLoadAppGameAlreadyLoadedMessage": "游戏已经启动", + "DialogLoadAppGameAlreadyLoadedSubMessage": "请停止模拟或关闭模拟器,再启动另一个游戏。", + "DialogUpdateAddUpdateErrorMessage": "选择的文件不是当前游戏的更新!", + "DialogSettingsBackendThreadingWarningTitle": "警告 - 图形引擎多线程", + "DialogSettingsBackendThreadingWarningMessage": "更改此选项后,必须重启 Ryujinx 模拟器才能生效。\n\n当启用图形引擎多线程时,根据显卡不同,您可能需要手动禁用显卡驱动程序自身的多线程(线程优化)。", + "DialogModManagerDeletionWarningMessage": "您即将删除 MOD:{0} \n\n确定吗?", + "DialogModManagerDeletionAllWarningMessage": "您即将删除该游戏的所有 MOD,\n\n确定吗?", "SettingsTabGraphicsFeaturesOptions": "功能", - "SettingsTabGraphicsBackendMultithreading": "多线程图形后端:", + "SettingsTabGraphicsBackendMultithreading": "图形引擎多线程:", "CommonAuto": "自动(推荐)", "CommonOff": "关闭", "CommonOn": "打开", @@ -406,8 +410,8 @@ "DialogProfileInvalidProfileNameErrorMessage": "文件名包含无效字符,请重试。", "MenuBarOptionsPauseEmulation": "暂停", "MenuBarOptionsResumeEmulation": "继续", - "AboutUrlTooltipMessage": "在浏览器中打开 Ryujinx 官网。", - "AboutDisclaimerMessage": "Ryujinx 以任何方式与 Nintendo™ 及其任何商业伙伴都没有关联", + "AboutUrlTooltipMessage": "在浏览器中打开 Ryujinx 模拟器官网。", + "AboutDisclaimerMessage": "Ryujinx 与 Nintendo™ 以及其合作伙伴没有任何关联。", "AboutAmiiboDisclaimerMessage": "我们的 Amiibo 模拟使用了\nAmiiboAPI (www.amiiboapi.com) ", "AboutPatreonUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 Patreon 赞助页。", "AboutGithubUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 GitHub 代码库。", @@ -415,216 +419,221 @@ "AboutTwitterUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 Twitter 主页。", "AboutRyujinxAboutTitle": "关于:", "AboutRyujinxAboutContent": "Ryujinx 是一款 Nintendo Switch™ 模拟器。\n您可以在 Patreon 上赞助 Ryujinx。\n关注 Twitter 或 Discord 可以获取模拟器最新动态。\n如果您对开发感兴趣,欢迎来 GitHub 或 Discord 加入我们!", - "AboutRyujinxMaintainersTitle": "由以下作者维护:", + "AboutRyujinxMaintainersTitle": "开发维护人员名单:", "AboutRyujinxMaintainersContentTooltipMessage": "在浏览器中打开贡献者页面", - "AboutRyujinxSupprtersTitle": "感谢 Patreon 的赞助者:", + "AboutRyujinxSupprtersTitle": "感谢 Patreon 上的赞助者:", "AmiiboSeriesLabel": "Amiibo 系列", "AmiiboCharacterLabel": "角色", "AmiiboScanButtonLabel": "扫描", - "AmiiboOptionsShowAllLabel": "显示所有 Amiibo 系列", - "AmiiboOptionsUsRandomTagLabel": "修复:使用随机标记的 UUID", - "DlcManagerTableHeadingEnabledLabel": "启用", - "DlcManagerTableHeadingTitleIdLabel": "游戏ID", + "AmiiboOptionsShowAllLabel": "显示所有 Amiibo", + "AmiiboOptionsUsRandomTagLabel": "修改:使用随机生成的Amiibo ID", + "DlcManagerTableHeadingEnabledLabel": "已启用", + "DlcManagerTableHeadingTitleIdLabel": "游戏 ID", "DlcManagerTableHeadingContainerPathLabel": "文件夹路径", "DlcManagerTableHeadingFullPathLabel": "完整路径", "DlcManagerRemoveAllButton": "全部删除", "DlcManagerEnableAllButton": "全部启用", - "DlcManagerDisableAllButton": "全部禁用", - "MenuBarOptionsChangeLanguage": "更改语言", + "DlcManagerDisableAllButton": "全部停用", + "ModManagerDeleteAllButton": "全部刪除", + "MenuBarOptionsChangeLanguage": "更改界面语言", "MenuBarShowFileTypes": "主页显示的文件类型", "CommonSort": "排序", "CommonShowNames": "显示名称", "CommonFavorite": "收藏", "OrderAscending": "从小到大", "OrderDescending": "从大到小", - "SettingsTabGraphicsFeatures": "功能与增强", + "SettingsTabGraphicsFeatures": "功能与优化", "ErrorWindowTitle": "错误窗口", - "ToggleDiscordTooltip": "控制是否在 Discord 中显示您的游玩状态", + "ToggleDiscordTooltip": "选择是否在 Discord 中显示您的游玩状态", "AddGameDirBoxTooltip": "输入要添加的游戏目录", "AddGameDirTooltip": "添加游戏目录到列表中", "RemoveGameDirTooltip": "移除选中的目录", - "CustomThemeCheckTooltip": "使用自定义UI主题来更改模拟器的外观样式", + "CustomThemeCheckTooltip": "使用自定义的 Avalonia 主题作为模拟器菜单的外观", "CustomThemePathTooltip": "自定义主题的目录", "CustomThemeBrowseTooltip": "查找自定义主题", - "DockModeToggleTooltip": "启用 Switch 的主机模式。\n绝大多数游戏画质会提高,略微增加性能消耗。\n在掌机和主机模式切换的过程中,您可能需要重新设置手柄类型。", - "DirectKeyboardTooltip": "开启 \"直连键盘访问(HID)支持\"\n(部分游戏可以使用您的键盘输入文字)", - "DirectMouseTooltip": "开启 \"直连鼠标访问(HID)支持\"\n(部分游戏可以使用您的鼠标导航)", + "DockModeToggleTooltip": "启用 Switch 的主机模式,可以模拟 Switch 连接底座的情况,此时绝大多数游戏画质会提高,略微增加性能消耗。\n若禁用主机模式,则使用 Switch 的掌机模式,可以模拟手持 Switch 运行游戏的情况,游戏画质会降低,性能消耗也会降低。\n\n如果使用主机模式,请选择“玩家 1”的手柄设置;如果使用掌机模式,请选择“掌机模式”的手柄设置。\n\n如果不确定,请保持开启状态。", + "DirectKeyboardTooltip": "直接键盘访问(HID)支持,游戏可以直接访问键盘作为文本输入设备。\n\n仅适用于在 Switch 硬件上原生支持键盘的游戏。\n\n如果不确定,请保持关闭状态。", + "DirectMouseTooltip": "直接鼠标访问(HID)支持,游戏可以直接访问鼠标作为指针输入设备。\n\n只适用于在 Switch 硬件上原生支持鼠标控制的游戏,这种游戏很少。\n\n启用后,触屏功能可能无法正常工作。\n\n如果不确定,请保持关闭状态。", "RegionTooltip": "更改系统区域", "LanguageTooltip": "更改系统语言", "TimezoneTooltip": "更改系统时区", - "TimeTooltip": "更改系统时钟", - "VSyncToggleTooltip": "关闭后,小部分游戏可以超过60FPS帧率,以获得高帧率体验。\n但是可能出现软锁或读盘时间增加。\n如不确定,就请保持开启状态。", - "PptcToggleTooltip": "缓存编译完成的游戏CPU指令。减少启动时间和卡顿,提高游戏响应速度。\n如不确定,就请保持开启状态。", - "FsIntegrityToggleTooltip": "检查游戏文件内容的完整性。\n遇到损坏的文件则记录到日志文件,有助于排查错误。\n对性能没有影响。\n如不确定,就请保持开启状态。", - "AudioBackendTooltip": "默认推荐SDL2,但每种音频后端对各类游戏兼容性不同,遇到音频问题可以尝试切换后端。", - "MemoryManagerTooltip": "改变 Switch 内存映射到电脑内存的方式,会影响CPU性能消耗。", - "MemoryManagerSoftwareTooltip": "使用软件内存页管理,最精确但是速度最慢。", - "MemoryManagerHostTooltip": "直接映射内存页到电脑内存,使得即时编译效率更高。", - "MemoryManagerUnsafeTooltip": "直接映射内存页,但不检查内存溢出,使得即时编译效率更高。\nRyujinx 可以访问任何位置的内存,因而相对不安全。\n此模式下只应运行您信任的游戏或软件(即官方游戏)。", - "UseHypervisorTooltip": "使用 Hypervisor 虚拟机代替即时编译。在可用的情况下能大幅提高性能。但目前可能不稳定。", - "DRamTooltip": "使用Switch开发机的内存布局。\n不会提高任何性能,某些高清纹理包或 4k 分辨率 MOD 可能需要此选项。\n如果不确定,请始终关闭该选项。", - "IgnoreMissingServicesTooltip": "开启后,游戏会忽略未实现的系统服务,从而继续运行。\n少部分新发布的游戏由于使用新的未知系统服务,可能需要此选项来避免闪退。\n模拟器更新完善系统服务之后,则无需开启选项。\n如您的游戏已经正常运行,请保持此选项关闭。", - "GraphicsBackendThreadingTooltip": "在第二个线程上执行图形后端命令。\n\n加速着色器编译,减少卡顿,提高 GPU 的性能。\n\n如果不确定,请设置为自动。", - "GalThreadingTooltip": "在第二个线程上执行图形后端命令。\n\n加速着色器编译,减少卡顿,提高 GPU 的性能。\n\n如果不确定,请设置为自动。", - "ShaderCacheToggleTooltip": "开启后,模拟器会保存编译完成的着色器到磁盘,减少游戏渲染新特效和场景时的卡顿。", - "ResolutionScaleTooltip": "缩放渲染的分辨率", - "ResolutionScaleEntryTooltip": "尽可能使用例如1.5的浮点倍数。非整数的倍率易引起 BUG。", - "AnisotropyTooltip": "各向异性过滤等级。提高倾斜视角纹理的清晰度\n('自动'使用游戏默认的等级)", - "AspectRatioTooltip": "渲染窗口的宽高比。", + "TimeTooltip": "更改系统时间", + "VSyncToggleTooltip": "模拟控制台的垂直同步,开启后会降低大部分游戏的帧率。关闭后,可以获得更高的帧率,但也可能导致游戏画面加载耗时更长或卡住。\n\n在游戏中可以使用热键进行切换(默认为 F1 键)。\n\n如果不确定,请保持开启状态。", + "PptcToggleTooltip": "缓存已编译的游戏指令,这样每次游戏加载时就无需重新编译。\n\n减少卡顿和启动时间,提高游戏响应速度。\n\n如果不确定,请保持开启状态。", + "FsIntegrityToggleTooltip": "启动游戏时检查游戏文件的完整性,并在日志中记录损坏的文件。\n\n对性能没有影响,用于排查故障。\n\n如果不确定,请保持开启状态。", + "AudioBackendTooltip": "更改音频处理引擎。\n\n推荐选择“SDL2”,另外“OpenAL”和“SoundIO”可以作为备选,选择“无”将没有声音。\n\n如果不确定,请设置为“SDL2”。", + "MemoryManagerTooltip": "更改模拟器内存映射和访问的方式,对模拟器CPU的性能影响很大。\n\n如果不确定,请设置为“跳过检查的本机映射”。", + "MemoryManagerSoftwareTooltip": "使用软件内存页管理,最准确但是速度最慢。", + "MemoryManagerHostTooltip": "直接映射内存页到电脑内存,使得即时编译和执行的效率更高。", + "MemoryManagerUnsafeTooltip": "直接映射内存页到电脑内存,并且不检查内存溢出,使得效率更高,但牺牲了安全。\n游戏程序可以访问模拟器内存的任意地址,所以不安全。\n建议此模式下只运行您信任的游戏程序。", + "UseHypervisorTooltip": "使用 Hypervisor 虚拟机代替即时编译,在可用的情况下能大幅提高性能,但目前可能还不稳定。", + "DRamTooltip": "模拟 Switch 开发机的内存布局。\n\n不会提高性能,某些高清纹理包或 4k 分辨率 MOD 可能需要使用此选项。\n\n如果不确定,请保持关闭状态。", + "IgnoreMissingServicesTooltip": "开启后,游戏会忽略未实现的系统服务,从而继续运行。\n少部分新发布的游戏由于使用了新的未知系统服务,可能需要此选项来避免闪退。\n模拟器更新完善系统服务之后,则无需开启此选项。\n\n如果不确定,请保持关闭状态。", + "GraphicsBackendThreadingTooltip": "在第二个线程上执行图形引擎指令。\n\n加速着色器编译,减少卡顿,可以提高 GPU 的性能。\n\n如果不确定,请设置为“自动”。", + "GalThreadingTooltip": "在第二个线程上执行图形引擎指令。\n\n加速着色器编译,减少卡顿,可以提高 GPU 的性能。\n\n如果不确定,请设置为“自动”。", + "ShaderCacheToggleTooltip": "模拟器将已编译的着色器保存到硬盘,减少游戏再次渲染相同图形导致的卡顿。\n\n如果不确定,请保持开启状态。", + "ResolutionScaleTooltip": "将游戏的渲染分辨率乘以一个倍数。\n\n有些游戏可能不适用这项设置,而且即使提高了分辨率仍然看起来像素化;对于这些游戏,您可能需要找到移除抗锯齿或提高内部渲染分辨率的 MOD。当使用这些 MOD 时,建议设置为“原生”。\n\n在游戏运行时,通过点击下面的“应用”按钮可以使设置生效;你可以将设置窗口移开,并试验找到您喜欢的游戏画面效果。\n\n请记住,对于几乎所有人而言,4倍分辨率都是过度的。", + "ResolutionScaleEntryTooltip": "建议设置为整数倍,带小数的分辨率缩放倍数(例如1.5),非整数倍的缩放容易导致问题或闪退。", + "AnisotropyTooltip": "各向异性过滤等级,可以提高倾斜视角纹理的清晰度。\n当设置为“自动”时,使用游戏自身设定的等级。", + "AspectRatioTooltip": "游戏渲染窗口的宽高比。\n\n只有当游戏使用了修改宽高比的 MOD 时才需要修改这个设置,否则图像会被拉伸。\n\n如果不确定,请保持为“16:9”。", "ShaderDumpPathTooltip": "转储图形着色器的路径", - "FileLogTooltip": "保存日志文件到硬盘。不会影响性能。", - "StubLogTooltip": "在控制台中打印 stub 日志消息。不影响性能。", - "InfoLogTooltip": "在控制台中打印信息日志消息。不影响性能。", - "WarnLogTooltip": "在控制台中打印警告日志消息。不影响性能。", - "ErrorLogTooltip": "打印控制台中的错误日志消息。不影响性能。", - "TraceLogTooltip": "在控制台中打印跟踪日志消息。不影响性能。", - "GuestLogTooltip": "在控制台中打印访客日志消息。不影响性能。", - "FileAccessLogTooltip": "在控制台中打印文件访问日志信息。", - "FSAccessLogModeTooltip": "启用访问日志输出到控制台。可能的模式为 0-3", - "DeveloperOptionTooltip": "谨慎使用", - "OpenGlLogLevel": "需要打开适当的日志等级", - "DebugLogTooltip": "记录Debug消息", - "LoadApplicationFileTooltip": "选择 Switch 支持的游戏格式并加载", - "LoadApplicationFolderTooltip": "选择解包后的 Switch 游戏并加载", - "OpenRyujinxFolderTooltip": "打开 Ryujinx 系统目录", + "FileLogTooltip": "将控制台日志保存到硬盘文件,不影响性能。", + "StubLogTooltip": "在控制台中显示存根日志,不影响性能。", + "InfoLogTooltip": "在控制台中显示信息日志,不影响性能。", + "WarnLogTooltip": "在控制台中显示警告日志,不影响性能。", + "ErrorLogTooltip": "在控制台中显示错误日志,不影响性能。", + "TraceLogTooltip": "在控制台中显示跟踪日志。", + "GuestLogTooltip": "在控制台中显示访客日志,不影响性能。", + "FileAccessLogTooltip": "在控制台中显示文件访问日志。", + "FSAccessLogModeTooltip": "在控制台中显示文件系统访问日志,可选模式为 0-3。", + "DeveloperOptionTooltip": "请谨慎使用", + "OpenGlLogLevel": "需要启用适当的日志级别", + "DebugLogTooltip": "在控制台中显示调试日志。\n\n仅在特别需要时使用此功能,因为它会导致日志信息难以阅读,并降低模拟器性能。", + "LoadApplicationFileTooltip": "选择 Switch 游戏文件并加载", + "LoadApplicationFolderTooltip": "选择解包后的 Switch 游戏目录并加载", + "OpenRyujinxFolderTooltip": "打开 Ryujinx 模拟器系统目录", "OpenRyujinxLogsTooltip": "打开日志存放的目录", - "ExitTooltip": "关闭 Ryujinx", + "ExitTooltip": "退出 Ryujinx 模拟器", "OpenSettingsTooltip": "打开设置窗口", - "OpenProfileManagerTooltip": "打开用户账户管理界面", - "StopEmulationTooltip": "停止运行当前游戏并回到主界面", + "OpenProfileManagerTooltip": "打开用户账户管理窗口", + "StopEmulationTooltip": "停止运行当前游戏,并回到主界面", "CheckUpdatesTooltip": "检查 Ryujinx 新版本", - "OpenAboutTooltip": "打开“关于”窗口", + "OpenAboutTooltip": "打开关于窗口", "GridSize": "网格尺寸", - "GridSizeTooltip": "调整网格模式的大小", + "GridSizeTooltip": "调整网格项目的大小", "SettingsTabSystemSystemLanguageBrazilianPortuguese": "巴西葡萄牙语", - "AboutRyujinxContributorsButtonHeader": "查看所有参与者", + "AboutRyujinxContributorsButtonHeader": "查看所有贡献者", "SettingsTabSystemAudioVolume": "音量:", "AudioVolumeTooltip": "调节音量", - "SettingsTabSystemEnableInternetAccess": "允许网络访问/局域网模式", - "EnableInternetAccessTooltip": "允许模拟的游戏进程访问互联网。\n当多个模拟器/真实的 Switch 连接到同一个局域网时,带有 LAN 模式的游戏可以相互通信。\n即使开启选项也无法访问 Nintendo 服务器。此外可能导致某些尝试联网的游戏崩溃。\n如果您不确定,请关闭该选项。", - "GameListContextMenuManageCheatToolTip": "管理金手指", + "SettingsTabSystemEnableInternetAccess": "启用网络连接(局域网)", + "EnableInternetAccessTooltip": "允许模拟的游戏程序访问网络。\n\n当多个模拟器或实体 Switch 连接到同一个网络时,带有局域网模式的游戏便可以相互通信。\n\n即使开启此选项也无法访问 Nintendo 服务器,有可能导致某些尝试联网的游戏闪退。\n\n如果不确定,请保持关闭状态。", + "GameListContextMenuManageCheatToolTip": "管理当前游戏的金手指", "GameListContextMenuManageCheat": "管理金手指", + "GameListContextMenuManageModToolTip": "管理当前游戏的 MOD", + "GameListContextMenuManageMod": "管理 MOD", "ControllerSettingsStickRange": "范围:", "DialogStopEmulationTitle": "Ryujinx - 停止模拟", - "DialogStopEmulationMessage": "是否确定停止模拟?", + "DialogStopEmulationMessage": "确定要停止模拟?", "SettingsTabCpu": "CPU", "SettingsTabAudio": "音频", "SettingsTabNetwork": "网络", "SettingsTabNetworkConnection": "网络连接", "SettingsTabCpuCache": "CPU 缓存", - "SettingsTabCpuMemory": "CPU 内存", - "DialogUpdaterFlatpakNotSupportedMessage": "请通过 FlatHub 更新 Ryujinx。", - "UpdaterDisabledWarningTitle": "更新已禁用!", - "GameListContextMenuOpenSdModsDirectory": "打开 Atmosphere MOD 目录", - "GameListContextMenuOpenSdModsDirectoryToolTip": "打开适用于 Atmosphere 自制系统的 MOD 目录", + "SettingsTabCpuMemory": "CPU 模式", + "DialogUpdaterFlatpakNotSupportedMessage": "请通过 FlatHub 更新 Ryujinx 模拟器。", + "UpdaterDisabledWarningTitle": "已禁用更新!", "ControllerSettingsRotate90": "顺时针旋转 90°", "IconSize": "图标尺寸", - "IconSizeTooltip": "更改游戏图标大小", + "IconSizeTooltip": "更改游戏图标的显示尺寸", "MenuBarOptionsShowConsole": "显示控制台", - "ShaderCachePurgeError": "清除着色器缓存时出错:{0}: {1}", - "UserErrorNoKeys": "找不到密钥", + "ShaderCachePurgeError": "清除 {0} 的着色器缓存时出错:{1}", + "UserErrorNoKeys": "找不到密钥Keys", "UserErrorNoFirmware": "找不到固件", - "UserErrorFirmwareParsingFailed": "固件解析错误", - "UserErrorApplicationNotFound": "找不到应用程序", + "UserErrorFirmwareParsingFailed": "固件解析出错", + "UserErrorApplicationNotFound": "找不到游戏程序", "UserErrorUnknown": "未知错误", "UserErrorUndefined": "未定义错误", - "UserErrorNoKeysDescription": "Ryujinx 找不到 'prod.keys' 文件", - "UserErrorNoFirmwareDescription": "Ryujinx 找不到任何已安装的固件", - "UserErrorFirmwareParsingFailedDescription": "Ryujinx 无法解密选择的固件。这通常是由于使用了过旧的密钥。", - "UserErrorApplicationNotFoundDescription": "Ryujinx 在选中路径找不到有效的应用程序。", - "UserErrorUnknownDescription": "发生未知错误!", - "UserErrorUndefinedDescription": "发生了未定义错误!此类错误不应出现,请联系开发人员!", - "OpenSetupGuideMessage": "打开设置教程", - "NoUpdate": "无更新", + "UserErrorNoKeysDescription": "Ryujinx 模拟器找不到“prod.keys”密钥文件", + "UserErrorNoFirmwareDescription": "Ryujinx 模拟器找不到任何已安装的固件", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx 模拟器无法解密当前固件,一般是由于使用了旧版的密钥导致的。", + "UserErrorApplicationNotFoundDescription": "Ryujinx 模拟器在所选路径中找不到有效的游戏程序。", + "UserErrorUnknownDescription": "出现未知错误!", + "UserErrorUndefinedDescription": "出现未定义错误!此类错误不应出现,请联系开发者!", + "OpenSetupGuideMessage": "打开安装指南", + "NoUpdate": "没有可用更新", "TitleUpdateVersionLabel": "版本 {0}", "RyujinxInfo": "Ryujinx - 信息", "RyujinxConfirm": "Ryujinx - 确认", "FileDialogAllTypes": "全部类型", "Never": "从不", - "SwkbdMinCharacters": "至少应为 {0} 个字长", - "SwkbdMinRangeCharacters": "必须为 {0}-{1} 个字长", - "SoftwareKeyboard": "软件键盘", - "SoftwareKeyboardModeNumbersOnly": "只接受数字", - "SoftwareKeyboardModeAlphabet": "只接受非中日韩文字", - "SoftwareKeyboardModeASCII": "只接受 ASCII 符号", - "DialogControllerAppletMessagePlayerRange": "游戏需要 {0} 个玩家并满足以下要求:\n\n手柄类型:{1}\n\n玩家类型:{2}\n\n{3} 请打开设置窗口,重新配置手柄输入;或者关闭返回。", - "DialogControllerAppletMessage": "游戏需要刚好 {0} 个玩家并满足以下要求:\n\n手柄类型:{1}\n\n玩家类型:{2}\n\n{3} 请打开设置窗口,重新配置手柄输入;或者关闭返回。", - "DialogControllerAppletDockModeSet": "目前处于主机模式,无法使用掌机操作方式", - "UpdaterRenaming": "正在删除旧文件...", + "SwkbdMinCharacters": "不少于 {0} 个字符", + "SwkbdMinRangeCharacters": "必须为 {0}-{1} 个字符", + "SoftwareKeyboard": "软键盘", + "SoftwareKeyboardModeNumeric": "只能输入 0-9 或 \".\"", + "SoftwareKeyboardModeAlphabet": "仅支持非中文字符", + "SoftwareKeyboardModeASCII": "仅支持 ASCII 字符", + "ControllerAppletControllers": "支持的手柄:", + "ControllerAppletPlayers": "玩家:", + "ControllerAppletDescription": "您当前的输入配置无效。打开设置并重新设置您的输入选项。", + "ControllerAppletDocked": "已经设置为主机模式,掌机手柄操控已经被禁用", + "UpdaterRenaming": "正在重命名旧文件...", "UpdaterRenameFailed": "更新过程中无法重命名文件:{0}", "UpdaterAddingFiles": "安装更新中...", "UpdaterExtracting": "正在提取更新...", - "UpdaterDownloading": "下载新版本中...", + "UpdaterDownloading": "下载更新中...", "Game": "游戏", "Docked": "主机模式", "Handheld": "掌机模式", "ConnectionError": "连接错误。", "AboutPageDeveloperListMore": "{0} 等开发者...", - "ApiError": "API错误。", + "ApiError": "API 错误。", "LoadingHeading": "正在启动 {0}", - "CompilingPPTC": "编译PPTC缓存中", + "CompilingPPTC": "编译 PPTC 缓存中", "CompilingShaders": "编译着色器中", "AllKeyboards": "所有键盘", - "OpenFileDialogTitle": "选择一个支持的文件以打开", - "OpenFolderDialogTitle": "选择一个包含解包游戏的文件夹", + "OpenFileDialogTitle": "选择支持的游戏文件并加载", + "OpenFolderDialogTitle": "选择包含解包游戏的目录并加载", "AllSupportedFormats": "所有支持的格式", - "RyujinxUpdater": "Ryujinx 更新程序", + "RyujinxUpdater": "Ryujinx 更新", "SettingsTabHotkeys": "快捷键", "SettingsTabHotkeysHotkeys": "键盘快捷键", - "SettingsTabHotkeysToggleVsyncHotkey": "切换垂直同步:", - "SettingsTabHotkeysScreenshotHotkey": "截屏:", - "SettingsTabHotkeysShowUiHotkey": "隐藏 界面:", + "SettingsTabHotkeysToggleVsyncHotkey": "开启或关闭垂直同步:", + "SettingsTabHotkeysScreenshotHotkey": "保存截屏:", + "SettingsTabHotkeysShowUiHotkey": "隐藏菜单栏和状态栏:", "SettingsTabHotkeysPauseHotkey": "暂停:", "SettingsTabHotkeysToggleMuteHotkey": "静音:", - "ControllerMotionTitle": "体感操作设置", + "ControllerMotionTitle": "体感设置", "ControllerRumbleTitle": "震动设置", "SettingsSelectThemeFileDialogTitle": "选择主题文件", "SettingsXamlThemeFile": "Xaml 主题文件", "AvatarWindowTitle": "管理账户 - 头像", "Amiibo": "Amiibo", "Unknown": "未知", - "Usage": "扫描可获得", + "Usage": "用法", "Writable": "可写入", "SelectDlcDialogTitle": "选择 DLC 文件", "SelectUpdateDialogTitle": "选择更新文件", + "SelectModDialogTitle": "选择 MOD 目录", "UserProfileWindowTitle": "管理用户账户", "CheatWindowTitle": "金手指管理器", "DlcWindowTitle": "管理 {0} ({1}) 的 DLC", "UpdateWindowTitle": "游戏更新管理器", "CheatWindowHeading": "适用于 {0} [{1}] 的金手指", - "BuildId": "游戏版本ID:", - "DlcWindowHeading": "{0} 个适用于 {1} ({2}) 的 DLC", - "UserProfilesEditProfile": "编辑选中账户", + "BuildId": "游戏版本 ID:", + "DlcWindowHeading": "{0} 个 DLC", + "ModWindowHeading": "{0} 个 MOD", + "UserProfilesEditProfile": "编辑所选", "Cancel": "取消", "Save": "保存", - "Discard": "返回", + "Discard": "放弃", + "Paused": "已暂停", "UserProfilesSetProfileImage": "选择头像", "UserProfileEmptyNameError": "必须输入名称", - "UserProfileNoImageError": "请选择您的头像", + "UserProfileNoImageError": "必须设置头像", "GameUpdateWindowHeading": "管理 {0} ({1}) 的更新", "SettingsTabHotkeysResScaleUpHotkey": "提高分辨率:", "SettingsTabHotkeysResScaleDownHotkey": "降低分辨率:", "UserProfilesName": "名称:", - "UserProfilesUserId": "用户ID:", - "SettingsTabGraphicsBackend": "图形后端", - "SettingsTabGraphicsBackendTooltip": "显卡使用的图形后端", - "SettingsEnableTextureRecompression": "启用纹理重压缩", - "SettingsEnableTextureRecompressionTooltip": "压缩某些纹理以减少显存的使用。\n适合显存小于 4GiB 的 GPU 开启。\n如果您不确定,请保持此项关闭。", - "SettingsTabGraphicsPreferredGpu": "首选 GPU", - "SettingsTabGraphicsPreferredGpuTooltip": "选择 Vulkan API 使用的显卡。\n此选项不会影响 OpenGL API。\n如果您不确定,建议选择\"dGPU(独立显卡)\"。如果没有独立显卡,则无需改动此选项。", - "SettingsAppRequiredRestartMessage": "Ryujinx 需要重启", - "SettingsGpuBackendRestartMessage": "您修改了图形 API 或显卡设置。需要重新启动才能生效", - "SettingsGpuBackendRestartSubMessage": "是否重启模拟器?", + "UserProfilesUserId": "用户 ID:", + "SettingsTabGraphicsBackend": "图形渲染引擎:", + "SettingsTabGraphicsBackendTooltip": "选择模拟器中使用的图像渲染引擎。\n\n安装了最新显卡驱动程序的所有现代显卡基本都支持 Vulkan,Vulkan 能够提供更快的着色器编译(较少的卡顿)。\n\n在旧版 Nvidia 显卡上、Linux 上的旧版 AMD 显卡,或者显存较低的显卡上,OpenGL 可能会取得更好的效果,但着色器编译更慢(更多的卡顿)。\n\n如果不确定,请设置为“Vulkan”。如果您的 GPU 已安装了最新显卡驱动程序也不支持 Vulkan,那请设置为“OpenGL”。", + "SettingsEnableTextureRecompression": "启用纹理压缩", + "SettingsEnableTextureRecompressionTooltip": "压缩 ASTC 纹理以减少 VRAM (显存)的占用。\n\n使用此纹理格式的游戏包括:异界锁链(Astral Chain),蓓优妮塔3(Bayonetta 3),火焰纹章Engage(Fire Emblem Engage),密特罗德 究极(Metroid Prime Remased),超级马力欧兄弟 惊奇(Super Mario Bros. Wonder)以及塞尔达传说 王国之泪(The Legend of Zelda: Tears of the Kingdom)。\n\n显存小于4GB的显卡在运行这些游戏时可能会偶发闪退。\n\n只有当您在上述游戏中的显存不足时才需要启用此选项。\n\n如果不确定,请保持关闭状态。", + "SettingsTabGraphicsPreferredGpu": "首选 GPU:", + "SettingsTabGraphicsPreferredGpuTooltip": "选择 Vulkan 图形引擎使用的 GPU。\n\n此选项不会影响 OpenGL 使用的 GPU。\n\n如果不确定,建议选择\"独立显卡(dGPU)\"。如果没有独立显卡,则无需改动此选项。", + "SettingsAppRequiredRestartMessage": "Ryujinx 模拟器需要重启", + "SettingsGpuBackendRestartMessage": "您修改了图形引擎或 GPU 设置,需要重启模拟器才能生效", + "SettingsGpuBackendRestartSubMessage": "是否要立即重启模拟器?", "RyujinxUpdaterMessage": "是否更新 Ryujinx 到最新的版本?", "SettingsTabHotkeysVolumeUpHotkey": "音量加:", "SettingsTabHotkeysVolumeDownHotkey": "音量减:", - "SettingsEnableMacroHLE": "启用 HLE 宏", - "SettingsEnableMacroHLETooltip": "GPU 宏代码的高级模拟。\n提高性能表现,但可能在某些游戏中引起图形错误。\n如果您不确定,请保持此项开启。", - "SettingsEnableColorSpacePassthrough": "颜色空间穿透", - "SettingsEnableColorSpacePassthroughTooltip": "指示 Vulkan 后端在不指定颜色空间的情况下传递颜色信息。对于具有宽色域显示器的用户来说,这可能会以颜色正确性为代价,产生更鲜艳的颜色。", + "SettingsEnableMacroHLE": "启用 HLE 宏加速", + "SettingsEnableMacroHLETooltip": "GPU 宏指令的高级模拟。\n\n提高性能表现,但一些游戏可能会出现图形错误。\n\n如果不确定,请保持开启状态。", + "SettingsEnableColorSpacePassthrough": "色彩空间直通", + "SettingsEnableColorSpacePassthroughTooltip": "使 Vulkan 图形引擎直接传输原始色彩信息。对于宽色域 (例如 DCI-P3) 显示器的用户来说,可以产生更鲜艳的颜色,代价是会损失部分色彩准确度。", "VolumeShort": "音量", "UserProfilesManageSaves": "管理存档", - "DeleteUserSave": "确定删除这个游戏的存档吗?", + "DeleteUserSave": "确定删除此游戏的用户存档吗?", "IrreversibleActionNote": "删除后不可恢复。", "SaveManagerHeading": "管理 {0} ({1}) 的存档", "SaveManagerTitle": "存档管理器", @@ -634,23 +643,26 @@ "UserProfilesRecoverLostAccounts": "恢复丢失的账户", "Recover": "恢复", "UserProfilesRecoverHeading": "找到了这些用户的存档数据", - "UserProfilesRecoverEmptyList": "没有可以恢复的配置文件", - "GraphicsAATooltip": "将抗锯齿使用到游戏渲染中", + "UserProfilesRecoverEmptyList": "没有可以恢复的用户数据", + "GraphicsAATooltip": "抗锯齿是一种图形处理技术,用于减少图像边缘的锯齿状现象,使图像更加平滑。\n\nFXAA(快速近似抗锯齿)是一种性能开销相对较小的抗锯齿方法,但可能会使得整体图像看起来有些模糊。\n\nSMAA(增强型子像素抗锯齿)则更加精细,它会尝试找到锯齿边缘并平滑它们,相比 FXAA 有更好的图像质量,但性能开销可能会稍大一些。\n\n如果开启了 FSR(FidelityFX Super Resolution,一种图像缩放过滤器)来提高性能或图像质量,不建议再启用抗锯齿,因为它们会产生不必要的图形处理开销,或者相互之间效果不协调。\n\n在游戏运行时,通过点击下面的“应用”按钮可以使设置生效;你可以将设置窗口移开,并试验找到您喜欢的游戏画面效果。\n\n如果不确定,请保持为“无”。", "GraphicsAALabel": "抗锯齿:", "GraphicsScalingFilterLabel": "缩放过滤:", - "GraphicsScalingFilterTooltip": "对帧缓冲区进行缩放", + "GraphicsScalingFilterTooltip": "选择在分辨率缩放时将使用的缩放过滤器。\n\nBilinear(双线性过滤)对于3D游戏效果较好,是一个安全的默认选项。\n\nNearest(最近邻过滤)推荐用于像素艺术游戏。\n\nFSR 1.0 只是一个锐化过滤器,不推荐与 FXAA 或 SMAA 抗锯齿一起使用。\n\n在游戏运行时,通过点击下面的“应用”按钮可以使设置生效;你可以将设置窗口移开,并试验找到您喜欢的游戏画面效果。\n\n如果不确定,请保持为“Bilinear”。", "GraphicsScalingFilterLevelLabel": "等级", - "GraphicsScalingFilterLevelTooltip": "设置缩放过滤级别", + "GraphicsScalingFilterLevelTooltip": "设置 FSR 1.0 的锐化等级,数值越高,图像越锐利。", "SmaaLow": "SMAA 低质量", "SmaaMedium": "SMAA 中质量", "SmaaHigh": "SMAA 高质量", - "SmaaUltra": "SMAA 极致质量", + "SmaaUltra": "SMAA 超高质量", "UserEditorTitle": "编辑用户", "UserEditorTitleCreate": "创建用户", "SettingsTabNetworkInterface": "网络接口:", - "NetworkInterfaceTooltip": "用于局域网功能的网络接口", + "NetworkInterfaceTooltip": "用于局域网(LAN)/本地网络发现(LDN)功能的网络接口。\n\n结合 VPN 或 XLink Kai 以及支持局域网功能的游戏,可以在互联网上伪造为同一网络连接。\n\n如果不确定,请保持为“默认”。", "NetworkInterfaceDefault": "默认", "PackagingShaders": "整合着色器中", - "AboutChangelogButton": "在Github上查看更新日志", - "AboutChangelogButtonTooltipMessage": "点击这里在您的默认浏览器中打开此版本的更新日志。" -} \ No newline at end of file + "AboutChangelogButton": "在 Github 上查看更新日志", + "AboutChangelogButtonTooltipMessage": "点击这里在浏览器中打开此版本的更新日志。", + "SettingsTabNetworkMultiplayer": "多人联机游玩", + "MultiplayerMode": "联机模式:", + "MultiplayerModeTooltip": "修改 LDN 多人联机游玩模式。\n\nLdnMitm 将修改游戏中的本地无线和本地游玩功能,使其表现得像局域网一样,允许和其他安装了 ldn_mitm 模块的 Ryujinx 模拟器和破解的任天堂 Switch 主机在同一网络下进行本地连接,实现多人联机游玩。\n\n多人联机游玩要求所有玩家必须运行相同的游戏版本(例如,任天堂明星大乱斗特别版 v13.0.1 无法与 v13.0.0 版本联机)。\n\n如果不确定,请保持为“禁用”。" +} diff --git a/src/Ryujinx/Assets/Locales/zh_TW.json b/src/Ryujinx/Assets/Locales/zh_TW.json index a2f59f60dd..d9df134f73 100644 --- a/src/Ryujinx/Assets/Locales/zh_TW.json +++ b/src/Ryujinx/Assets/Locales/zh_TW.json @@ -1,43 +1,43 @@ { - "Language": "英文 (美國)", - "MenuBarFileOpenApplet": "開啟 Applet 應用程序", - "MenuBarFileOpenAppletOpenMiiAppletToolTip": "開啟獨立的Mii修改器應用程序", - "SettingsTabInputDirectMouseAccess": "滑鼠直接操作", - "SettingsTabSystemMemoryManagerMode": "記憶體管理模式:", - "SettingsTabSystemMemoryManagerModeSoftware": "軟體", - "SettingsTabSystemMemoryManagerModeHost": "主機模式 (快速)", - "SettingsTabSystemMemoryManagerModeHostUnchecked": "主機略過檢查模式 (最快, 但不安全)", + "Language": "繁體中文 (台灣)", + "MenuBarFileOpenApplet": "開啟小程式", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "在獨立模式下開啟 Mii 編輯器小程式", + "SettingsTabInputDirectMouseAccess": "滑鼠直接存取", + "SettingsTabSystemMemoryManagerMode": "記憶體管理員模式:", + "SettingsTabSystemMemoryManagerModeSoftware": "軟體模式", + "SettingsTabSystemMemoryManagerModeHost": "主體模式 (快速)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "主體略過檢查模式 (最快,不安全)", "SettingsTabSystemUseHypervisor": "使用 Hypervisor", - "MenuBarFile": "_檔案", - "MenuBarFileOpenFromFile": "_載入檔案", - "MenuBarFileOpenUnpacked": "載入_已解開封裝的遊戲", + "MenuBarFile": "檔案(_F)", + "MenuBarFileOpenFromFile": "從檔案載入應用程式(_L)", + "MenuBarFileOpenUnpacked": "載入解開封裝的遊戲(_U)", "MenuBarFileOpenEmuFolder": "開啟 Ryujinx 資料夾", "MenuBarFileOpenLogsFolder": "開啟日誌資料夾", - "MenuBarFileExit": "_退出", - "MenuBarOptions": "選項", + "MenuBarFileExit": "結束(_E)", + "MenuBarOptions": "選項(_O)", "MenuBarOptionsToggleFullscreen": "切換全螢幕模式", "MenuBarOptionsStartGamesInFullscreen": "使用全螢幕模式啟動遊戲", "MenuBarOptionsStopEmulation": "停止模擬", - "MenuBarOptionsSettings": "_設定", - "MenuBarOptionsManageUserProfiles": "_管理使用者帳戶", - "MenuBarActions": "_動作", + "MenuBarOptionsSettings": "設定(_S)", + "MenuBarOptionsManageUserProfiles": "管理使用者設定檔(_M)", + "MenuBarActions": "動作(_A)", "MenuBarOptionsSimulateWakeUpMessage": "模擬喚醒訊息", "MenuBarActionsScanAmiibo": "掃描 Amiibo", - "MenuBarTools": "_工具", + "MenuBarTools": "工具(_T)", "MenuBarToolsInstallFirmware": "安裝韌體", "MenuBarFileToolsInstallFirmwareFromFile": "從 XCI 或 ZIP 安裝韌體", "MenuBarFileToolsInstallFirmwareFromDirectory": "從資料夾安裝韌體", "MenuBarToolsManageFileTypes": "管理檔案類型", - "MenuBarToolsInstallFileTypes": "註冊檔案類型", - "MenuBarToolsUninstallFileTypes": "取消註冊檔案類型", - "MenuBarHelp": "幫助", + "MenuBarToolsInstallFileTypes": "安裝檔案類型", + "MenuBarToolsUninstallFileTypes": "移除檔案類型", + "MenuBarHelp": "說明(_H)", "MenuBarHelpCheckForUpdates": "檢查更新", "MenuBarHelpAbout": "關於", "MenuSearch": "搜尋...", - "GameListHeaderFavorite": "收藏", + "GameListHeaderFavorite": "我的最愛", "GameListHeaderIcon": "圖示", "GameListHeaderApplication": "名稱", - "GameListHeaderDeveloper": "開發人員", + "GameListHeaderDeveloper": "開發者", "GameListHeaderVersion": "版本", "GameListHeaderTimePlayed": "遊玩時數", "GameListHeaderLastPlayed": "最近遊玩", @@ -45,66 +45,71 @@ "GameListHeaderFileSize": "檔案大小", "GameListHeaderPath": "路徑", "GameListContextMenuOpenUserSaveDirectory": "開啟使用者存檔資料夾", - "GameListContextMenuOpenUserSaveDirectoryToolTip": "開啟此遊戲的存檔資料夾", - "GameListContextMenuOpenDeviceSaveDirectory": "開啟系統資料夾", - "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "開啟此遊戲的系統設定資料夾", - "GameListContextMenuOpenBcatSaveDirectory": "開啟 BCAT 資料夾", - "GameListContextMenuOpenBcatSaveDirectoryToolTip": "開啟此遊戲的 BCAT 資料夾\n", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "開啟此應用程式的使用者存檔資料夾", + "GameListContextMenuOpenDeviceSaveDirectory": "開啟裝置存檔資料夾", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "開啟此應用程式的裝置存檔資料夾", + "GameListContextMenuOpenBcatSaveDirectory": "開啟 BCAT 存檔資料夾", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "開啟此應用程式的 BCAT 存檔資料夾", "GameListContextMenuManageTitleUpdates": "管理遊戲更新", "GameListContextMenuManageTitleUpdatesToolTip": "開啟遊戲更新管理視窗", "GameListContextMenuManageDlc": "管理 DLC", "GameListContextMenuManageDlcToolTip": "開啟 DLC 管理視窗", - "GameListContextMenuOpenModsDirectory": "開啟模組資料夾", - "GameListContextMenuOpenModsDirectoryToolTip": "開啟此遊戲的模組資料夾", "GameListContextMenuCacheManagement": "快取管理", - "GameListContextMenuCacheManagementPurgePptc": "清除 PPTC 快取", - "GameListContextMenuCacheManagementPurgePptcToolTip": "刪除遊戲的 PPTC 快取", + "GameListContextMenuCacheManagementPurgePptc": "佇列 PPTC 重建", + "GameListContextMenuCacheManagementPurgePptcToolTip": "下一次啟動遊戲時,觸發 PPTC 進行重建", "GameListContextMenuCacheManagementPurgeShaderCache": "清除著色器快取", - "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "刪除遊戲的著色器快取", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "刪除應用程式的著色器快取", "GameListContextMenuCacheManagementOpenPptcDirectory": "開啟 PPTC 資料夾", - "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "開啟此遊戲的 PPTC 快取資料夾", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "開啟此應用程式的 PPTC 快取資料夾", "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "開啟著色器快取資料夾", - "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "開啟此遊戲的著色器快取資料夾", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "開啟此應用程式的著色器快取資料夾", "GameListContextMenuExtractData": "提取資料", "GameListContextMenuExtractDataExeFS": "ExeFS", - "GameListContextMenuExtractDataExeFSToolTip": "從遊戲的目前狀態中提取 ExeFS 分區(包含更新)", + "GameListContextMenuExtractDataExeFSToolTip": "從應用程式的目前配置中提取 ExeFS 分區 (包含更新)", "GameListContextMenuExtractDataRomFS": "RomFS", - "GameListContextMenuExtractDataRomFSToolTip": "從遊戲的目前狀態中提取 RomFS 分區(包含更新)", - "GameListContextMenuExtractDataLogo": "圖示", - "GameListContextMenuExtractDataLogoToolTip": "從遊戲的目前狀態中提取圖示(包含更新)", - "StatusBarGamesLoaded": "{0}/{1} 遊戲載入完成", - "StatusBarSystemVersion": "系統版本: {0}", - "LinuxVmMaxMapCountDialogTitle": "檢測到映射的記憶體上限過低", - "LinuxVmMaxMapCountDialogTextPrimary": "你願意增加 vm.max_map_count to {0} 的數值嗎?", - "LinuxVmMaxMapCountDialogTextSecondary": "遊戲佔用的記憶體超出了映射的上限. Ryujinx的處理程序即將面臨崩潰.", - "LinuxVmMaxMapCountDialogButtonUntilRestart": "碓定 (直至下一次重新啟動)", - "LinuxVmMaxMapCountDialogButtonPersistent": "碓定 (永遠設定)", - "LinuxVmMaxMapCountWarningTextPrimary": "映射記憶體的最大值少於目前建議的下限.", - "LinuxVmMaxMapCountWarningTextSecondary": "目前 vm.max_map_count ({0}) 的數值少於 {1}. 遊戲佔用的記憶體超出了映射的上限. Ryujinx的處理程序即將面臨崩潰.\n\n你可能需要手動增加上限或安裝 pkexec, 這些都能協助Ryujinx完成此操作.", + "GameListContextMenuExtractDataRomFSToolTip": "從應用程式的目前配置中提取 RomFS 分區 (包含更新)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "從應用程式的目前配置中提取 Logo 分區 (包含更新)", + "GameListContextMenuCreateShortcut": "建立應用程式捷徑", + "GameListContextMenuCreateShortcutToolTip": "建立桌面捷徑,啟動選取的應用程式", + "GameListContextMenuCreateShortcutToolTipMacOS": "在 macOS 的應用程式資料夾中建立捷徑,啟動選取的應用程式", + "GameListContextMenuOpenModsDirectory": "開啟模組資料夾", + "GameListContextMenuOpenModsDirectoryToolTip": "開啟此應用程式模組的資料夾", + "GameListContextMenuOpenSdModsDirectory": "開啟 Atmosphere 模組資料夾", + "GameListContextMenuOpenSdModsDirectoryToolTip": "開啟此應用程式模組的另一個 SD 卡 Atmosphere 資料夾。適用於為真實硬體封裝的模組。", + "StatusBarGamesLoaded": "{0}/{1} 遊戲已載入", + "StatusBarSystemVersion": "系統版本: {0}", + "LinuxVmMaxMapCountDialogTitle": "檢測到記憶體映射的低限值", + "LinuxVmMaxMapCountDialogTextPrimary": "您是否要將 vm.max_map_count 的數值增至 {0}?", + "LinuxVmMaxMapCountDialogTextSecondary": "某些遊戲可能會嘗試建立超過目前允許的記憶體映射。一旦超過此限制,Ryujinx 就會崩潰。", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "是的,直到下次重新啟動", + "LinuxVmMaxMapCountDialogButtonPersistent": "是的,永久設定", + "LinuxVmMaxMapCountWarningTextPrimary": "記憶體映射的最大值低於建議值。", + "LinuxVmMaxMapCountWarningTextSecondary": "目前 vm.max_map_count ({0}) 的數值小於 {1}。某些遊戲可能會嘗試建立比目前允許值更多的記憶體映射。一旦超過此限制,Ryujinx 就會崩潰。\n\n您可能需要手動提高上限,或者安裝 pkexec,讓 Ryujinx 協助提高上限。", "Settings": "設定", "SettingsTabGeneral": "使用者介面", "SettingsTabGeneralGeneral": "一般", "SettingsTabGeneralEnableDiscordRichPresence": "啟用 Discord 動態狀態展示", - "SettingsTabGeneralCheckUpdatesOnLaunch": "自動檢查更新", - "SettingsTabGeneralShowConfirmExitDialog": "顯示「確認離開」對話框", - "SettingsTabGeneralHideCursor": "隱藏滑鼠遊標:", - "SettingsTabGeneralHideCursorNever": "永不", - "SettingsTabGeneralHideCursorOnIdle": "自動隱藏滑鼠", + "SettingsTabGeneralCheckUpdatesOnLaunch": "啟動時檢查更新", + "SettingsTabGeneralShowConfirmExitDialog": "顯示「確認結束」對話方塊", + "SettingsTabGeneralHideCursor": "隱藏滑鼠游標:", + "SettingsTabGeneralHideCursorNever": "從不", + "SettingsTabGeneralHideCursorOnIdle": "閒置時", "SettingsTabGeneralHideCursorAlways": "總是", "SettingsTabGeneralGameDirectories": "遊戲資料夾", "SettingsTabGeneralAdd": "新增", "SettingsTabGeneralRemove": "刪除", "SettingsTabSystem": "系統", "SettingsTabSystemCore": "核心", - "SettingsTabSystemSystemRegion": "系統區域:", + "SettingsTabSystemSystemRegion": "系統區域:", "SettingsTabSystemSystemRegionJapan": "日本", "SettingsTabSystemSystemRegionUSA": "美國", "SettingsTabSystemSystemRegionEurope": "歐洲", "SettingsTabSystemSystemRegionAustralia": "澳洲", "SettingsTabSystemSystemRegionChina": "中國", "SettingsTabSystemSystemRegionKorea": "韓國", - "SettingsTabSystemSystemRegionTaiwan": "台灣", - "SettingsTabSystemSystemLanguage": "系統語言:", + "SettingsTabSystemSystemRegionTaiwan": "台灣 (中華民國)", + "SettingsTabSystemSystemLanguage": "系統語言:", "SettingsTabSystemSystemLanguageJapanese": "日文", "SettingsTabSystemSystemLanguageAmericanEnglish": "英文 (美國)", "SettingsTabSystemSystemLanguageFrench": "法文", @@ -118,70 +123,70 @@ "SettingsTabSystemSystemLanguageRussian": "俄文", "SettingsTabSystemSystemLanguageTaiwanese": "中文 (台灣)", "SettingsTabSystemSystemLanguageBritishEnglish": "英文 (英國)", - "SettingsTabSystemSystemLanguageCanadianFrench": "加拿大法語", - "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "拉丁美洲西班牙文", + "SettingsTabSystemSystemLanguageCanadianFrench": "加拿大法文", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "美洲西班牙文", "SettingsTabSystemSystemLanguageSimplifiedChinese": "簡體中文", - "SettingsTabSystemSystemLanguageTraditionalChinese": "繁體中文", + "SettingsTabSystemSystemLanguageTraditionalChinese": "正體中文 (建議)", "SettingsTabSystemSystemTimeZone": "系統時區:", "SettingsTabSystemSystemTime": "系統時鐘:", "SettingsTabSystemEnableVsync": "垂直同步", - "SettingsTabSystemEnablePptc": "啟用 PPTC 快取", - "SettingsTabSystemEnableFsIntegrityChecks": "開啟檔案系統完整性檢查", - "SettingsTabSystemAudioBackend": "音效處理後台架構:", - "SettingsTabSystemAudioBackendDummy": "模擬", + "SettingsTabSystemEnablePptc": "PPTC (剖析式持久轉譯快取, Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "檔案系統完整性檢查", + "SettingsTabSystemAudioBackend": "音效後端:", + "SettingsTabSystemAudioBackendDummy": "虛設 (Dummy)", "SettingsTabSystemAudioBackendOpenAL": "OpenAL", "SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSDL2": "SDL2", - "SettingsTabSystemHacks": "修正", - "SettingsTabSystemHacksNote": " (會引起模擬器不穩定)", - "SettingsTabSystemExpandDramSize": "使用額外的記憶體佈局 (開發人員)", - "SettingsTabSystemIgnoreMissingServices": "忽略缺少的服務", - "SettingsTabGraphics": "圖像", - "SettingsTabGraphicsAPI": "圖像處理應用程式介面", + "SettingsTabSystemHacks": "補釘修正", + "SettingsTabSystemHacksNote": "可能導致不穩定", + "SettingsTabSystemExpandDramSize": "使用其他記憶體配置 (開發者)", + "SettingsTabSystemIgnoreMissingServices": "忽略遺失的服務", + "SettingsTabGraphics": "圖形", + "SettingsTabGraphicsAPI": "圖形 API", "SettingsTabGraphicsEnableShaderCache": "啟用著色器快取", - "SettingsTabGraphicsAnisotropicFiltering": "各向異性過濾:", + "SettingsTabGraphicsAnisotropicFiltering": "各向異性過濾:", "SettingsTabGraphicsAnisotropicFilteringAuto": "自動", "SettingsTabGraphicsAnisotropicFiltering2x": "2 倍", "SettingsTabGraphicsAnisotropicFiltering4x": "4 倍", "SettingsTabGraphicsAnisotropicFiltering8x": "8 倍", - "SettingsTabGraphicsAnisotropicFiltering16x": "16倍", - "SettingsTabGraphicsResolutionScale": "解析度比例:", + "SettingsTabGraphicsAnisotropicFiltering16x": "16 倍", + "SettingsTabGraphicsResolutionScale": "解析度比例:", "SettingsTabGraphicsResolutionScaleCustom": "自訂 (不建議使用)", "SettingsTabGraphicsResolutionScaleNative": "原生 (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2 倍 (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3 倍 (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4 倍 (2880p/4320p)", - "SettingsTabGraphicsAspectRatio": "螢幕長寬比例:", - "SettingsTabGraphicsAspectRatio4x3": "4:3", - "SettingsTabGraphicsAspectRatio16x9": "16:9", - "SettingsTabGraphicsAspectRatio16x10": "16:10", - "SettingsTabGraphicsAspectRatio21x9": "21:9", - "SettingsTabGraphicsAspectRatio32x9": "32:9", - "SettingsTabGraphicsAspectRatioStretch": "伸展至螢幕大小", + "SettingsTabGraphicsResolutionScale4x": "4 倍 (2880p/4320p) (不建議使用)", + "SettingsTabGraphicsAspectRatio": "顯示長寬比例:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "拉伸以適應視窗", "SettingsTabGraphicsDeveloperOptions": "開發者選項", - "SettingsTabGraphicsShaderDumpPath": "圖形著色器轉存路徑:", + "SettingsTabGraphicsShaderDumpPath": "圖形著色器傾印路徑:", "SettingsTabLogging": "日誌", "SettingsTabLoggingLogging": "日誌", - "SettingsTabLoggingEnableLoggingToFile": "儲存記錄日誌為檔案", - "SettingsTabLoggingEnableStubLogs": "啟用 Stub 記錄", - "SettingsTabLoggingEnableInfoLogs": "啟用資訊記錄", - "SettingsTabLoggingEnableWarningLogs": "啟用警告記錄", - "SettingsTabLoggingEnableErrorLogs": "啟用錯誤記錄", - "SettingsTabLoggingEnableTraceLogs": "啟用追蹤記錄", - "SettingsTabLoggingEnableGuestLogs": "啟用賓客記錄", - "SettingsTabLoggingEnableFsAccessLogs": "啟用檔案存取記錄", - "SettingsTabLoggingFsGlobalAccessLogMode": "記錄全域檔案存取模式:", + "SettingsTabLoggingEnableLoggingToFile": "啟用日誌到檔案", + "SettingsTabLoggingEnableStubLogs": "啟用 Stub 日誌", + "SettingsTabLoggingEnableInfoLogs": "啟用資訊日誌", + "SettingsTabLoggingEnableWarningLogs": "啟用警告日誌", + "SettingsTabLoggingEnableErrorLogs": "啟用錯誤日誌", + "SettingsTabLoggingEnableTraceLogs": "啟用追蹤日誌", + "SettingsTabLoggingEnableGuestLogs": "啟用客體日誌", + "SettingsTabLoggingEnableFsAccessLogs": "啟用檔案系統存取日誌", + "SettingsTabLoggingFsGlobalAccessLogMode": "檔案系統全域存取日誌模式:", "SettingsTabLoggingDeveloperOptions": "開發者選項", - "SettingsTabLoggingDeveloperOptionsNote": "警告:此操作會降低效能", - "SettingsTabLoggingGraphicsBackendLogLevel": "圖像處理後台記錄等級:", + "SettingsTabLoggingDeveloperOptionsNote": "警告: 會降低效能", + "SettingsTabLoggingGraphicsBackendLogLevel": "圖形後端日誌等級:", "SettingsTabLoggingGraphicsBackendLogLevelNone": "無", "SettingsTabLoggingGraphicsBackendLogLevelError": "錯誤", "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "減速", "SettingsTabLoggingGraphicsBackendLogLevelAll": "全部", - "SettingsTabLoggingEnableDebugLogs": "啟用除錯記錄", + "SettingsTabLoggingEnableDebugLogs": "啟用偵錯日誌", "SettingsTabInput": "輸入", - "SettingsTabInputEnableDockedMode": "Docked 模式", - "SettingsTabInputDirectKeyboardAccess": "鍵盤直接操作", + "SettingsTabInputEnableDockedMode": "底座模式", + "SettingsTabInputDirectKeyboardAccess": "鍵盤直接存取", "SettingsButtonSave": "儲存", "SettingsButtonClose": "關閉", "SettingsButtonOk": "確定", @@ -196,20 +201,20 @@ "ControllerSettingsPlayer6": "玩家 6", "ControllerSettingsPlayer7": "玩家 7", "ControllerSettingsPlayer8": "玩家 8", - "ControllerSettingsHandheld": "掌機模式", + "ControllerSettingsHandheld": "手提模式", "ControllerSettingsInputDevice": "輸入裝置", - "ControllerSettingsRefresh": "更新", - "ControllerSettingsDeviceDisabled": "關閉", + "ControllerSettingsRefresh": "重新整理", + "ControllerSettingsDeviceDisabled": "停用", "ControllerSettingsControllerType": "控制器類型", - "ControllerSettingsControllerTypeHandheld": "掌機", - "ControllerSettingsControllerTypeProController": "Nintendo Switch Pro控制器", - "ControllerSettingsControllerTypeJoyConPair": "JoyCon", + "ControllerSettingsControllerTypeHandheld": "手提模式", + "ControllerSettingsControllerTypeProController": "Pro 控制器", + "ControllerSettingsControllerTypeJoyConPair": "雙 JoyCon", "ControllerSettingsControllerTypeJoyConLeft": "左 JoyCon", "ControllerSettingsControllerTypeJoyConRight": "右 JoyCon", - "ControllerSettingsProfile": "配置檔案", + "ControllerSettingsProfile": "設定檔", "ControllerSettingsProfileDefault": "預設", "ControllerSettingsLoad": "載入", - "ControllerSettingsAdd": "建立", + "ControllerSettingsAdd": "新增", "ControllerSettingsRemove": "刪除", "ControllerSettingsButtons": "按鍵", "ControllerSettingsButtonA": "A", @@ -231,13 +236,13 @@ "ControllerSettingsStickStick": "搖桿", "ControllerSettingsStickInvertXAxis": "搖桿左右反向", "ControllerSettingsStickInvertYAxis": "搖桿上下反向", - "ControllerSettingsStickDeadzone": "盲區:", + "ControllerSettingsStickDeadzone": "無感帶:", "ControllerSettingsLStick": "左搖桿", "ControllerSettingsRStick": "右搖桿", - "ControllerSettingsTriggersLeft": "左 Triggers", - "ControllerSettingsTriggersRight": "右 Triggers", - "ControllerSettingsTriggersButtonsLeft": "左 Triggers 鍵", - "ControllerSettingsTriggersButtonsRight": "右 Triggers 鍵", + "ControllerSettingsTriggersLeft": "左扳機", + "ControllerSettingsTriggersRight": "右扳機", + "ControllerSettingsTriggersButtonsLeft": "左扳機鍵", + "ControllerSettingsTriggersButtonsRight": "右扳機鍵", "ControllerSettingsTriggers": "板機", "ControllerSettingsTriggerL": "L", "ControllerSettingsTriggerR": "R", @@ -250,407 +255,414 @@ "ControllerSettingsExtraButtonsLeft": "左按鍵", "ControllerSettingsExtraButtonsRight": "右按鍵", "ControllerSettingsMisc": "其他", - "ControllerSettingsTriggerThreshold": "Triggers 閾值:", - "ControllerSettingsMotion": "傳感器", - "ControllerSettingsMotionUseCemuhookCompatibleMotion": "使用 CemuHook 相容性傳感協定", - "ControllerSettingsMotionControllerSlot": "控制器插槽:", + "ControllerSettingsTriggerThreshold": "扳機閾值:", + "ControllerSettingsMotion": "體感", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "使用與 CemuHook 相容的體感", + "ControllerSettingsMotionControllerSlot": "控制器插槽:", "ControllerSettingsMotionMirrorInput": "鏡像輸入", - "ControllerSettingsMotionRightJoyConSlot": "右 JoyCon:", - "ControllerSettingsMotionServerHost": "伺服器IP地址:", - "ControllerSettingsMotionGyroSensitivity": "陀螺儀敏感度:", - "ControllerSettingsMotionGyroDeadzone": "陀螺儀盲區:", + "ControllerSettingsMotionRightJoyConSlot": "右 JoyCon 插槽:", + "ControllerSettingsMotionServerHost": "伺服器主機位址:", + "ControllerSettingsMotionGyroSensitivity": "陀螺儀靈敏度:", + "ControllerSettingsMotionGyroDeadzone": "陀螺儀無感帶:", "ControllerSettingsSave": "儲存", "ControllerSettingsClose": "關閉", - "UserProfilesSelectedUserProfile": "選擇使用者帳戶:", - "UserProfilesSaveProfileName": "儲存帳戶名稱", - "UserProfilesChangeProfileImage": "更換帳戶頭像", - "UserProfilesAvailableUserProfiles": "現有的使用者帳戶:", - "UserProfilesAddNewProfile": "建立帳戶", + "UserProfilesSelectedUserProfile": "選取的使用者設定檔:", + "UserProfilesSaveProfileName": "儲存設定檔名稱", + "UserProfilesChangeProfileImage": "變更設定檔圖像", + "UserProfilesAvailableUserProfiles": "可用的使用者設定檔:", + "UserProfilesAddNewProfile": "建立設定檔", "UserProfilesDelete": "刪除", "UserProfilesClose": "關閉", - "ProfileNameSelectionWatermark": "選擇一個暱稱", - "ProfileImageSelectionTitle": "帳戶頭像選擇", - "ProfileImageSelectionHeader": "選擇帳戶頭像", - "ProfileImageSelectionNote": "你可以導入自訂頭像,或從系統中選擇頭像", - "ProfileImageSelectionImportImage": "導入圖片檔案", - "ProfileImageSelectionSelectAvatar": "選擇系統頭像", - "InputDialogTitle": "輸入對話框", - "InputDialogOk": "完成", + "ProfileNameSelectionWatermark": "選擇暱稱", + "ProfileImageSelectionTitle": "設定檔圖像選取", + "ProfileImageSelectionHeader": "選擇設定檔圖像", + "ProfileImageSelectionNote": "您可以匯入自訂的設定檔圖像,或從系統韌體中選取大頭貼。", + "ProfileImageSelectionImportImage": "匯入圖像檔案", + "ProfileImageSelectionSelectAvatar": "選取韌體大頭貼", + "InputDialogTitle": "輸入對話方塊", + "InputDialogOk": "確定", "InputDialogCancel": "取消", - "InputDialogAddNewProfileTitle": "選擇帳戶名稱", - "InputDialogAddNewProfileHeader": "請輸入帳戶名稱", - "InputDialogAddNewProfileSubtext": "(最大長度:{0})", - "AvatarChoose": "選擇", + "InputDialogAddNewProfileTitle": "選擇設定檔名稱", + "InputDialogAddNewProfileHeader": "請輸入設定檔名稱", + "InputDialogAddNewProfileSubtext": "(最大長度: {0})", + "AvatarChoose": "選擇大頭貼", "AvatarSetBackgroundColor": "設定背景顏色", "AvatarClose": "關閉", - "ControllerSettingsLoadProfileToolTip": "載入配置檔案", - "ControllerSettingsAddProfileToolTip": "新增配置檔案", - "ControllerSettingsRemoveProfileToolTip": "刪除配置檔案", - "ControllerSettingsSaveProfileToolTip": "儲存配置檔案", - "MenuBarFileToolsTakeScreenshot": "儲存截圖", - "MenuBarFileToolsHideUi": "隱藏使用者介面", - "GameListContextMenuRunApplication": "執行程式", - "GameListContextMenuToggleFavorite": "標記為收藏", - "GameListContextMenuToggleFavoriteToolTip": "啟用或取消收藏標記", - "SettingsTabGeneralTheme": "佈景主題", - "SettingsTabGeneralThemeCustomTheme": "自訂佈景主題路徑", - "SettingsTabGeneralThemeBaseStyle": "基本佈景主題式樣", - "SettingsTabGeneralThemeBaseStyleDark": "深色模式", - "SettingsTabGeneralThemeBaseStyleLight": "淺色模式", - "SettingsTabGeneralThemeEnableCustomTheme": "使用自訂佈景主題", - "ButtonBrowse": "瀏覽", + "ControllerSettingsLoadProfileToolTip": "載入設定檔", + "ControllerSettingsAddProfileToolTip": "新增設定檔", + "ControllerSettingsRemoveProfileToolTip": "刪除設定檔", + "ControllerSettingsSaveProfileToolTip": "儲存設定檔", + "MenuBarFileToolsTakeScreenshot": "儲存擷取畫面", + "MenuBarFileToolsHideUi": "隱藏 UI", + "GameListContextMenuRunApplication": "執行應用程式", + "GameListContextMenuToggleFavorite": "加入/移除為我的最愛", + "GameListContextMenuToggleFavoriteToolTip": "切換遊戲的我的最愛狀態", + "SettingsTabGeneralTheme": "佈景主題:", + "SettingsTabGeneralThemeDark": "深色", + "SettingsTabGeneralThemeLight": "淺色", "ControllerSettingsConfigureGeneral": "配置", "ControllerSettingsRumble": "震動", "ControllerSettingsRumbleStrongMultiplier": "強震動調節", "ControllerSettingsRumbleWeakMultiplier": "弱震動調節", - "DialogMessageSaveNotAvailableMessage": "沒有{0} [{1:x16}]的遊戲存檔", - "DialogMessageSaveNotAvailableCreateSaveMessage": "是否建立該遊戲的存檔資料夾?", - "DialogConfirmationTitle": "Ryujinx - 設定", - "DialogUpdaterTitle": "Ryujinx - 更新", + "DialogMessageSaveNotAvailableMessage": "沒有 {0} [{1:x16}] 的存檔", + "DialogMessageSaveNotAvailableCreateSaveMessage": "您想為這款遊戲建立存檔嗎?", + "DialogConfirmationTitle": "Ryujinx - 確認", + "DialogUpdaterTitle": "Ryujinx - 更新程式", "DialogErrorTitle": "Ryujinx - 錯誤", "DialogWarningTitle": "Ryujinx - 警告", - "DialogExitTitle": "Ryujinx - 關閉", + "DialogExitTitle": "Ryujinx - 結束", "DialogErrorMessage": "Ryujinx 遇到了錯誤", - "DialogExitMessage": "你確定要關閉 Ryujinx 嗎?", - "DialogExitSubMessage": "所有未儲存的資料將會遺失!", - "DialogMessageCreateSaveErrorMessage": "建立特定的存檔時出現錯誤: {0}", - "DialogMessageFindSaveErrorMessage": "查找特定的存檔時出現錯誤: {0}", + "DialogExitMessage": "您確定要關閉 Ryujinx 嗎?", + "DialogExitSubMessage": "所有未儲存的資料將會遺失!", + "DialogMessageCreateSaveErrorMessage": "建立指定的存檔時出現錯誤: {0}", + "DialogMessageFindSaveErrorMessage": "尋找指定的存檔時出現錯誤: {0}", "FolderDialogExtractTitle": "選擇要解壓到的資料夾", - "DialogNcaExtractionMessage": "提取{1}的{0}分區...", - "DialogNcaExtractionTitle": "Ryujinx - NCA分區提取", - "DialogNcaExtractionMainNcaNotFoundErrorMessage": "提取失敗。所選檔案中不含主NCA檔案", - "DialogNcaExtractionCheckLogErrorMessage": "提取失敗。請查看日誌檔案取得詳情。", + "DialogNcaExtractionMessage": "從 {1} 提取 {0} 分區...", + "DialogNcaExtractionTitle": "Ryujinx - NCA 分區提取器", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "提取失敗。所選檔案中不存在主 NCA 檔案。", + "DialogNcaExtractionCheckLogErrorMessage": "提取失敗。請閱讀日誌檔案了解更多資訊。", "DialogNcaExtractionSuccessMessage": "提取成功。", - "DialogUpdaterConvertFailedMessage": "無法轉換目前 Ryujinx 版本。", - "DialogUpdaterCancelUpdateMessage": "更新取消!", - "DialogUpdaterAlreadyOnLatestVersionMessage": "你使用的 Ryujinx 是最新版本。", - "DialogUpdaterFailedToGetVersionMessage": "嘗試從 Github 取得版本訊息時失敗。可能是因為 GitHub Actions 正在編譯新版本。請於數分數後重試。", - "DialogUpdaterConvertFailedGithubMessage": "無法轉換從 Github 接收到的 Ryujinx 版本。", - "DialogUpdaterDownloadingMessage": "下載最新版本中...", + "DialogUpdaterConvertFailedMessage": "無法轉換目前的 Ryujinx 版本。", + "DialogUpdaterCancelUpdateMessage": "取消更新!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "您已經在使用最新版本的 Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "嘗試從 GitHub Release 取得發布資訊時發生錯誤。如果 GitHub Actions 正在編譯新版本,則可能會出現這種情況。請幾分鐘後再試一次。", + "DialogUpdaterConvertFailedGithubMessage": "無法轉換從 Github Release 接收到的 Ryujinx 版本。", + "DialogUpdaterDownloadingMessage": "正在下載更新...", "DialogUpdaterExtractionMessage": "正在提取更新...", - "DialogUpdaterRenamingMessage": "正在刪除舊檔案...", - "DialogUpdaterAddingFilesMessage": "安裝更新中...", + "DialogUpdaterRenamingMessage": "重新命名更新...", + "DialogUpdaterAddingFilesMessage": "加入新更新...", "DialogUpdaterCompleteMessage": "更新成功!", - "DialogUpdaterRestartMessage": "你確定要立即重新啟動 Ryujinx 嗎?", - "DialogUpdaterArchNotSupportedMessage": "你執行的系統架構不被支援!", - "DialogUpdaterArchNotSupportedSubMessage": "(僅支援 x64 系統)", - "DialogUpdaterNoInternetMessage": "你沒有連接到網際網絡!", - "DialogUpdaterNoInternetSubMessage": "請確保網際網絡連接正常!", - "DialogUpdaterDirtyBuildMessage": "不能更新非官方版本的 Ryujinx!", - "DialogUpdaterDirtyBuildSubMessage": "如果你希望使用被受支援的Ryujinx版本,請你在官方網址 https://ryujinx.org/ 下載.", - "DialogRestartRequiredMessage": "模擬器必須重新啟動", - "DialogThemeRestartMessage": "佈景主題設定已儲存。需要重新啟動才能生效。", - "DialogThemeRestartSubMessage": "你確定要現在重新啟動嗎?", - "DialogFirmwareInstallEmbeddedMessage": "要安裝遊戲內建的韌體嗎?(韌體 {0})", + "DialogUpdaterRestartMessage": "您現在要重新啟動 Ryujinx 嗎?", + "DialogUpdaterNoInternetMessage": "您沒有連線到網際網路!", + "DialogUpdaterNoInternetSubMessage": "請確認您的網際網路連線正常!", + "DialogUpdaterDirtyBuildMessage": "您無法更新非官方版本的 Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "如果您正在尋找受官方支援的版本,請從 https://ryujinx.org/ 下載 Ryujinx。", + "DialogRestartRequiredMessage": "需要重新啟動", + "DialogThemeRestartMessage": "佈景主題設定已儲存。需要重新啟動才能套用主題。", + "DialogThemeRestartSubMessage": "您要重新啟動嗎", + "DialogFirmwareInstallEmbeddedMessage": "您想安裝遊戲內建的韌體嗎? (韌體 {0})", "DialogFirmwareInstallEmbeddedSuccessMessage": "未找到已安裝的韌體,但 Ryujinx 可以從現有的遊戲安裝韌體{0}.\\n模擬器現在可以執行。", "DialogFirmwareNoFirmwareInstalledMessage": "未安裝韌體", "DialogFirmwareInstalledMessage": "已安裝韌體{0}", - "DialogInstallFileTypesSuccessMessage": "成功註冊檔案類型!", - "DialogInstallFileTypesErrorMessage": "註冊檔案類型失敗。", - "DialogUninstallFileTypesSuccessMessage": "成功取消註冊檔案類型!", - "DialogUninstallFileTypesErrorMessage": "取消註冊檔案類型失敗。", + "DialogInstallFileTypesSuccessMessage": "成功安裝檔案類型!", + "DialogInstallFileTypesErrorMessage": "無法安裝檔案類型。", + "DialogUninstallFileTypesSuccessMessage": "成功移除檔案類型!", + "DialogUninstallFileTypesErrorMessage": "無法移除檔案類型。", "DialogOpenSettingsWindowLabel": "開啟設定視窗", - "DialogControllerAppletTitle": "控制器小視窗", - "DialogMessageDialogErrorExceptionMessage": "顯示訊息對話框時出現錯誤: {0}", - "DialogSoftwareKeyboardErrorExceptionMessage": "顯示軟體鍵盤時出現錯誤: {0}", - "DialogErrorAppletErrorExceptionMessage": "顯示錯誤對話框時出現錯誤: {0}", - "DialogUserErrorDialogMessage": "{0}: {1}", - "DialogUserErrorDialogInfoMessage": "\n有關修復此錯誤的更多訊息,可以遵循我們的設定指南。", + "DialogControllerAppletTitle": "控制器小程式", + "DialogMessageDialogErrorExceptionMessage": "顯示訊息對話方塊時出現錯誤: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "顯示軟體鍵盤時出現錯誤: {0}", + "DialogErrorAppletErrorExceptionMessage": "顯示錯誤對話方塊時出現錯誤: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\n有關如何修復此錯誤的更多資訊,請參閱我們的設定指南。", "DialogUserErrorDialogTitle": "Ryujinx 錯誤 ({0})", - "DialogAmiiboApiTitle": "Amiibo 應用程式介面", - "DialogAmiiboApiFailFetchMessage": "從 API 取得訊息時出錯。", - "DialogAmiiboApiConnectErrorMessage": "無法連接到 Amiibo API 伺服器。伺服器可能已關閉,或你沒有連接到網際網路。", - "DialogProfileInvalidProfileErrorMessage": "配置檔案 {0} 與目前輸入系統不相容。", - "DialogProfileDefaultProfileOverwriteErrorMessage": "無法覆蓋預設的配置檔案", - "DialogProfileDeleteProfileTitle": "刪除帳戶", - "DialogProfileDeleteProfileMessage": "此操作不可撤銷, 您確定要繼續嗎?", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "從 API 取得資訊時出現錯誤。", + "DialogAmiiboApiConnectErrorMessage": "無法連接 Amiibo API 伺服器。服務可能已停機,或者您可能需要確認網際網路連線是否在線上。", + "DialogProfileInvalidProfileErrorMessage": "設定檔 {0} 與目前輸入配置系統不相容。", + "DialogProfileDefaultProfileOverwriteErrorMessage": "無法覆蓋預設設定檔", + "DialogProfileDeleteProfileTitle": "刪除設定檔", + "DialogProfileDeleteProfileMessage": "此動作不可復原,您確定要繼續嗎?", "DialogWarning": "警告", - "DialogPPTCDeletionMessage": "下一次重啟時將會重新建立以下遊戲的 PPTC 快取\n\n{0}\n\n你確定要繼續嗎?", - "DialogPPTCDeletionErrorMessage": "清除位於{0}的 PPTC 快取時出錯: {1}", - "DialogShaderDeletionMessage": "即將刪除以下遊戲的著色器快取:\n\n{0}\n\n你確定要繼續嗎?", - "DialogShaderDeletionErrorMessage": "清除{0}的著色器快取時出現錯誤: {1}", + "DialogPPTCDeletionMessage": "您將在下一次啟動時佇列重建以下遊戲的 PPTC:\n\n{0}\n\n您確定要繼續嗎?", + "DialogPPTCDeletionErrorMessage": "在 {0} 清除 PPTC 快取時出錯: {1}", + "DialogShaderDeletionMessage": "您將刪除以下遊戲的著色器快取:\n\n{0}\n\n您確定要繼續嗎?", + "DialogShaderDeletionErrorMessage": "在 {0} 清除著色器快取時出錯: {1}", "DialogRyujinxErrorMessage": "Ryujinx 遇到錯誤", - "DialogInvalidTitleIdErrorMessage": "UI 錯誤:所選遊戲沒有有效的標題ID", - "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "路徑{0}找不到有效的系統韌體。", - "DialogFirmwareInstallerFirmwareInstallTitle": "安裝韌體{0}", - "DialogFirmwareInstallerFirmwareInstallMessage": "將安裝{0}版本的系統。", - "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n這將替換目前系統版本{0}。", - "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "你確定要繼續嗎?", - "DialogFirmwareInstallerFirmwareInstallWaitMessage": "安裝韌體中...", - "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "成功安裝系統版本{0}。", - "DialogUserProfileDeletionWarningMessage": "刪除後將沒有可選擇的使用者帳戶", - "DialogUserProfileDeletionConfirmMessage": "你確定要刪除選擇中的帳戶嗎?", - "DialogUserProfileUnsavedChangesTitle": "警告 - 有未儲存的更改", - "DialogUserProfileUnsavedChangesMessage": "你對此帳戶所做的更改尚未儲存.", - "DialogUserProfileUnsavedChangesSubMessage": "你確定要捨棄更改嗎?", - "DialogControllerSettingsModifiedConfirmMessage": "目前的輸入配置檔案已更新", - "DialogControllerSettingsModifiedConfirmSubMessage": "你確定要儲存嗎?", - "DialogLoadNcaErrorMessage": "{0}. 錯誤的檔案: {1}", - "DialogDlcNoDlcErrorMessage": "選擇的檔案不包含所選遊戲的 DLC!", - "DialogPerformanceCheckLoggingEnabledMessage": "你啟用了跟蹤記錄,它的設計僅限開發人員使用。", - "DialogPerformanceCheckLoggingEnabledConfirmMessage": "為了獲得最佳效能,建議停用追蹤記錄。你是否要立即停用?", - "DialogPerformanceCheckShaderDumpEnabledMessage": "你啟用了著色器轉存,它的設計僅限開發人員使用。", - "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "為了獲得最佳效能,建議停用著色器轉存。你是否要立即停用?", - "DialogLoadAppGameAlreadyLoadedMessage": "目前已載入此遊戲", - "DialogLoadAppGameAlreadyLoadedSubMessage": "請停止模擬或關閉程式,再啟動另一個遊戲。", - "DialogUpdateAddUpdateErrorMessage": "選擇的檔案不包含所選遊戲的更新!", - "DialogSettingsBackendThreadingWarningTitle": "警告 - 後台多工執行中", - "DialogSettingsBackendThreadingWarningMessage": "更改此選項後必須重啟 Ryujinx 才能生效。根據你的硬體,您開啟該選項時,可能需要手動停用驅動程式本身的GPU多執行緒。", + "DialogInvalidTitleIdErrorMessage": "UI 錯誤: 所選遊戲沒有有效的遊戲 ID", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "在 {0} 中未發現有效的系統韌體。", + "DialogFirmwareInstallerFirmwareInstallTitle": "安裝韌體 {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "將安裝系統版本 {0}。", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n這將取代目前的系統版本 {0}。", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n您確定要繼續嗎?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "正在安裝韌體...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "成功安裝系統版本 {0}。", + "DialogUserProfileDeletionWarningMessage": "如果刪除選取的設定檔,將無法開啟其他設定檔", + "DialogUserProfileDeletionConfirmMessage": "您是否要刪除所選設定檔", + "DialogUserProfileUnsavedChangesTitle": "警告 - 未儲存的變更", + "DialogUserProfileUnsavedChangesMessage": "您對該使用者設定檔所做的變更尚未儲存。", + "DialogUserProfileUnsavedChangesSubMessage": "您確定要放棄變更嗎?", + "DialogControllerSettingsModifiedConfirmMessage": "目前控制器設定已更新。", + "DialogControllerSettingsModifiedConfirmSubMessage": "您想要儲存嗎?", + "DialogLoadFileErrorMessage": "{0}。出錯檔案: {1}", + "DialogModAlreadyExistsMessage": "模組已經存在", + "DialogModInvalidMessage": "指定資料夾不包含模組!", + "DialogModDeleteNoParentMessage": "刪除失敗: 無法找到模組「{0}」的父資料夾!", + "DialogDlcNoDlcErrorMessage": "指定檔案不包含所選遊戲的 DLC!", + "DialogPerformanceCheckLoggingEnabledMessage": "您已啟用追蹤日誌,該功能僅供開發者使用。", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "為獲得最佳效能,建議停用追蹤日誌。您是否要立即停用追蹤日誌嗎?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "您已啟用著色器傾印,該功能僅供開發者使用。", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "為獲得最佳效能,建議停用著色器傾印。您是否要立即停用著色器傾印嗎?", + "DialogLoadAppGameAlreadyLoadedMessage": "已載入此遊戲", + "DialogLoadAppGameAlreadyLoadedSubMessage": "請停止模擬或關閉模擬器,然後再啟動另一款遊戲。", + "DialogUpdateAddUpdateErrorMessage": "指定檔案不包含所選遊戲的更新!", + "DialogSettingsBackendThreadingWarningTitle": "警告 - 後端執行緒處理中", + "DialogSettingsBackendThreadingWarningMessage": "變更此選項後,必須重新啟動 Ryujinx 才能完全生效。使用 Ryujinx 的多執行緒功能時,可能需要手動停用驅動程式本身的多執行緒功能,這取決於您的平台。", + "DialogModManagerDeletionWarningMessage": "您將刪除模組: {0}\n\n您確定要繼續嗎?", + "DialogModManagerDeletionAllWarningMessage": "您即將刪除此遊戲的所有模組。\n\n您確定要繼續嗎?", "SettingsTabGraphicsFeaturesOptions": "功能", - "SettingsTabGraphicsBackendMultithreading": "圖像處理後台多線程支援:", - "CommonAuto": "自動(推薦)", + "SettingsTabGraphicsBackendMultithreading": "圖形後端多執行緒:", + "CommonAuto": "自動", "CommonOff": "關閉", - "CommonOn": "打開", + "CommonOn": "開啟", "InputDialogYes": "是", "InputDialogNo": "否", - "DialogProfileInvalidProfileNameErrorMessage": "檔案名包含無效字元,請重試。", + "DialogProfileInvalidProfileNameErrorMessage": "檔案名稱包含無效字元。請重試。", "MenuBarOptionsPauseEmulation": "暫停", "MenuBarOptionsResumeEmulation": "繼續", - "AboutUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的官方網站。", - "AboutDisclaimerMessage": "Ryujinx 與 Nintendo™ 並沒有任何關聯, 包括其合作伙伴, 及任何形式上。", - "AboutAmiiboDisclaimerMessage": "我們的 Amiibo 模擬使用了\nAmiiboAPI (www.amiiboapi.com) ", - "AboutPatreonUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的 Patreon 贊助網頁。", - "AboutGithubUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的 GitHub 儲存庫。", - "AboutDiscordUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的 Discord 伺服器邀請連結。", - "AboutTwitterUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的 Twitter 首頁。", - "AboutRyujinxAboutTitle": "關於:", - "AboutRyujinxAboutContent": "Ryujinx 是 Nintendo Switch™ 的一款模擬器。\n懇請您在 Patreon 上贊助我們。\n關注 Twitter 或 Discord 可以取得我們的最新動態。\n如果您對開發本軟體感興趣,歡迎來 GitHub 和 Discord 加入我們!", - "AboutRyujinxMaintainersTitle": "開發及維護名單:", - "AboutRyujinxMaintainersContentTooltipMessage": "在瀏覽器中打開貢獻者的網頁", - "AboutRyujinxSupprtersTitle": "Patreon 的贊助人:", + "AboutUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 網站。", + "AboutDisclaimerMessage": "Ryujinx 和 Nintendo™\n或其任何合作夥伴完全沒有關聯。", + "AboutAmiiboDisclaimerMessage": "我們在 Amiibo 模擬中\n使用了 AmiiboAPI (www.amiiboapi.com)。", + "AboutPatreonUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 的 Patreon 網頁。", + "AboutGithubUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 的 GitHub 網頁。", + "AboutDiscordUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 的 Discord 邀請連結。", + "AboutTwitterUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 的 Twitter 網頁。", + "AboutRyujinxAboutTitle": "關於:", + "AboutRyujinxAboutContent": "Ryujinx 是一款 Nintendo Switch™ 模擬器。\n請在 Patreon 上支持我們。\n關注我們的 Twitter 或 Discord 取得所有最新消息。\n對於有興趣貢獻的開發者,可以在我們的 GitHub 或 Discord 上了解更多資訊。", + "AboutRyujinxMaintainersTitle": "維護者:", + "AboutRyujinxMaintainersContentTooltipMessage": "在預設瀏覽器中開啟貢獻者的網頁", + "AboutRyujinxSupprtersTitle": "Patreon 支持者:", "AmiiboSeriesLabel": "Amiibo 系列", "AmiiboCharacterLabel": "角色", "AmiiboScanButtonLabel": "掃描", "AmiiboOptionsShowAllLabel": "顯示所有 Amiibo", - "AmiiboOptionsUsRandomTagLabel": "侵略:使用隨機標記的 Uuid 編碼", - "DlcManagerTableHeadingEnabledLabel": "已啟用", - "DlcManagerTableHeadingTitleIdLabel": "遊戲ID", - "DlcManagerTableHeadingContainerPathLabel": "資料夾路徑", + "AmiiboOptionsUsRandomTagLabel": "補釘修正: 使用隨機標記的 Uuid", + "DlcManagerTableHeadingEnabledLabel": "啟用", + "DlcManagerTableHeadingTitleIdLabel": "遊戲 ID", + "DlcManagerTableHeadingContainerPathLabel": "容器路徑", "DlcManagerTableHeadingFullPathLabel": "完整路徑", "DlcManagerRemoveAllButton": "全部刪除", - "DlcManagerEnableAllButton": "啟用全部", - "DlcManagerDisableAllButton": "停用全部", - "MenuBarOptionsChangeLanguage": "更改語言", + "DlcManagerEnableAllButton": "全部啟用", + "DlcManagerDisableAllButton": "全部停用", + "ModManagerDeleteAllButton": "全部刪除", + "MenuBarOptionsChangeLanguage": "變更語言", "MenuBarShowFileTypes": "顯示檔案類型", "CommonSort": "排序", "CommonShowNames": "顯示名稱", - "CommonFavorite": "收藏", + "CommonFavorite": "我的最愛", "OrderAscending": "從小到大", "OrderDescending": "從大到小", - "SettingsTabGraphicsFeatures": "功能及優化", + "SettingsTabGraphicsFeatures": "功能與改進", "ErrorWindowTitle": "錯誤視窗", "ToggleDiscordTooltip": "啟用或關閉 Discord 動態狀態展示", - "AddGameDirBoxTooltip": "輸入要添加的遊戲資料夾", - "AddGameDirTooltip": "添加遊戲資料夾到列表中", - "RemoveGameDirTooltip": "移除選擇中的遊戲資料夾", - "CustomThemeCheckTooltip": "啟用或關閉自訂佈景主題", - "CustomThemePathTooltip": "自訂佈景主題的資料夾", - "CustomThemeBrowseTooltip": "查找自訂佈景主題", - "DockModeToggleTooltip": "是否開啟 Switch 的 Docked 模式", - "DirectKeyboardTooltip": "支援鍵盤直接存取 (HID協定) . 可供給遊戲使用你的鍵盤作為輸入文字裝置.", - "DirectMouseTooltip": "支援滑鼠直接存取 (HID協定) . 可供給遊戲使用你的滑鼠作為瞄準裝置.", - "RegionTooltip": "更改系統區域", - "LanguageTooltip": "更改系統語言", - "TimezoneTooltip": "更改系統時區", - "TimeTooltip": "更改系統時鐘", - "VSyncToggleTooltip": "模擬遊戲主機垂直同步更新頻率. 重要地反映著遊戲本身的速度; 關閉它可能會令後使用動態更新率的遊戲速度過高, 或會引致載入錯誤等等.\n\n可在遊戲中利用自訂快速鍵開關此功能. 我們也建議使用快速鍵, 如果你計劃關上它.\n\n如果不確定請設定為\"開啟\".", - "PptcToggleTooltip": "開啟以後減少遊戲啟動時間和卡頓", - "FsIntegrityToggleTooltip": "是否檢查遊戲檔案內容的完整性", - "AudioBackendTooltip": "更改音效處理後台架構.\n\n推薦使用SDL2架構, 而OpenAL及SoundIO架構用作後備. Dummy是沒有音效的.\n\n如果不確定請設定為\"SDL2\".", - "MemoryManagerTooltip": "更改模擬器記憶體至電腦記憶體的映射和存取方式,極其影響CPU效能.\n\n如果不確定, 請設定為\"主機略過檢查模式\".", - "MemoryManagerSoftwareTooltip": "使用軟體虛擬分頁表換算, 最精確但是速度最慢.", - "MemoryManagerHostTooltip": "直接地映射模擬記憶體到電腦記憶體. 對 JIT 編譯和執行效率有顯著提升. ", - "MemoryManagerUnsafeTooltip": "直接地映射模擬記憶體到電腦記憶體, 但是不檢查記憶體溢出. 由於 Ryujinx 的子程式可以存取任何位置的記憶體, 因而相對不安全. 故在此模式下只應執行你信任的遊戲或軟體.", - "UseHypervisorTooltip": "使用 Hypervisor 代替 JIT。在本功能可用時就可以大幅增大效能,但目前狀態還不穩定。", - "DRamTooltip": "利用可選擇性的記憶體模式來模擬Switch發展中型號.\n\n此選項只會對高畫質材質包或4K模組有用. 而這並不會提升效能. \n\n如果不確定請關閉本功能.", - "IgnoreMissingServicesTooltip": "忽略某些未被實施的系統服務. 此功能有助於繞過當啟動遊戲時帶來的故障.\n\n如果不確定請關閉本功能。", - "GraphicsBackendThreadingTooltip": "執行雙線程後台繪圖指令, 能夠減少著色器編譯斷續, 並提高GPU驅動效能, 即將它不支持多線程處理. 而對於多線程處理也有少量提升.\n\n如果你不確定請設定為\"自動\"", - "GalThreadingTooltip": "執行雙線程後台繪圖指令.\n\n能夠加速著色器編譯及減少斷續, 並提高GPU驅動效能, 即將它不支持多線程處理. 而對於多線程處理也有少量提升.\n\n如果你不確定請設定為\"自動\"", - "ShaderCacheToggleTooltip": "儲存著色器快取到硬碟,減少存取斷續。\n\n如果不確定請設定為\"開啟\"。", - "ResolutionScaleTooltip": "解析度繪圖倍率", - "ResolutionScaleEntryTooltip": "盡量使用如1.5的浮點倍數。非整數的倍率易引起錯誤", - "AnisotropyTooltip": "各向異性過濾等級。提高傾斜視角材質的清晰度\n(選擇「自動」將使用遊戲預設指定的等級)", - "AspectRatioTooltip": "模擬器視窗解析度的長寬比", - "ShaderDumpPathTooltip": "圖形著色器轉存路徑", - "FileLogTooltip": "是否儲存日誌檔案到硬碟", - "StubLogTooltip": "在控制台顯示及記錄 Stub 訊息", - "InfoLogTooltip": "在控制台顯示及記錄資訊訊息", - "WarnLogTooltip": "在控制台顯示及記錄警告訊息\n", - "ErrorLogTooltip": "在控制台顯示及記錄錯誤訊息", - "TraceLogTooltip": "在控制台顯示及記錄追蹤訊息", - "GuestLogTooltip": "在控制台顯示及記錄賓客訊息", - "FileAccessLogTooltip": "在控制台顯示及記錄檔案存取訊息", - "FSAccessLogModeTooltip": "在控制台顯示及記錄FS 存取訊息. 可選的模式是 0-3", - "DeveloperOptionTooltip": "使用請謹慎", - "OpenGlLogLevel": "需要打開適當的日誌等級", - "DebugLogTooltip": "在控制台顯示及記錄除錯訊息.\n\n僅限於受訓的成員使用, 因為它很難理解而且令模擬的效能非常地差.\n", - "LoadApplicationFileTooltip": "選擇 Switch 支援的遊戲格式並載入", - "LoadApplicationFolderTooltip": "選擇解包後的 Switch 遊戲並載入", - "OpenRyujinxFolderTooltip": "開啟 Ryujinx 系統資料夾", - "OpenRyujinxLogsTooltip": "開啟存放日誌的資料夾", - "ExitTooltip": "關閉 Ryujinx", + "AddGameDirBoxTooltip": "輸入要新增到清單中的遊戲資料夾", + "AddGameDirTooltip": "新增遊戲資料夾到清單中", + "RemoveGameDirTooltip": "移除選取的遊戲資料夾", + "CustomThemeCheckTooltip": "為圖形使用者介面使用自訂 Avalonia 佈景主題,變更模擬器功能表的外觀", + "CustomThemePathTooltip": "自訂 GUI 佈景主題的路徑", + "CustomThemeBrowseTooltip": "瀏覽自訂 GUI 佈景主題", + "DockModeToggleTooltip": "底座模式可使模擬系統表現為底座的 Nintendo Switch。這可以提高大多數遊戲的圖形保真度。反之,停用該模式將使模擬系統表現為手提模式的 Nintendo Switch,從而降低圖形品質。\n\n如果計劃使用底座模式,請配置玩家 1 控制;如果計劃使用手提模式,請配置手提控制。\n\n如果不確定,請保持開啟狀態。", + "DirectKeyboardTooltip": "支援直接鍵盤存取 (HID)。遊戲可將鍵盤作為文字輸入裝置。\n\n僅適用於在 Switch 硬體上原生支援使用鍵盤的遊戲。\n\n如果不確定,請保持關閉狀態。", + "DirectMouseTooltip": "支援滑鼠直接存取 (HID)。遊戲可將滑鼠作為指向裝置使用。\n\n僅適用於在 Switch 硬體上原生支援滑鼠控制的遊戲,這類遊戲很少。\n\n啟用後,觸控螢幕功能可能無法使用。\n\n如果不確定,請保持關閉狀態。", + "RegionTooltip": "變更系統區域", + "LanguageTooltip": "變更系統語言", + "TimezoneTooltip": "變更系統時區", + "TimeTooltip": "變更系統時鐘", + "VSyncToggleTooltip": "模擬遊戲機的垂直同步。對大多數遊戲來說,它本質上是一個幀率限制器;停用它可能會導致遊戲以更高的速度執行,或使載入畫面耗時更長或卡住。\n\n可以在遊戲中使用快速鍵進行切換 (預設為 F1)。如果您打算停用,我們建議您這樣做。\n\n如果不確定,請保持開啟狀態。", + "PptcToggleTooltip": "儲存已轉譯的 JIT 函數,這樣每次載入遊戲時就無需再轉譯這些函數。\n\n減少遊戲首次啟動後的卡頓現象,並大大加快啟動時間。\n\n如果不確定,請保持開啟狀態。", + "FsIntegrityToggleTooltip": "在啟動遊戲時檢查損壞的檔案,如果檢測到損壞的檔案,則在日誌中顯示雜湊值錯誤。\n\n對效能沒有影響,旨在幫助排除故障。\n\n如果不確定,請保持開啟狀態。", + "AudioBackendTooltip": "變更用於繪製音訊的後端。\n\nSDL2 是首選,而 OpenAL 和 SoundIO 則作為備用。虛設 (Dummy) 將沒有聲音。\n\n如果不確定,請設定為 SDL2。", + "MemoryManagerTooltip": "變更客體記憶體的映射和存取方式。這會極大地影響模擬 CPU 效能。\n\n如果不確定,請設定為主體略過檢查模式。", + "MemoryManagerSoftwareTooltip": "使用軟體分頁表進行位址轉換。精度最高,但效能最差。", + "MemoryManagerHostTooltip": "直接映射主體位址空間中的記憶體。更快的 JIT 編譯和執行速度。", + "MemoryManagerUnsafeTooltip": "直接映射記憶體,但在存取前不封鎖客體位址空間內的位址。速度更快,但相對不安全。訪客應用程式可以從 Ryujinx 中的任何地方存取記憶體,因此只能使用該模式執行您信任的程式。", + "UseHypervisorTooltip": "使用 Hypervisor 取代 JIT。使用時可大幅提高效能,但在目前狀態下可能不穩定。", + "DRamTooltip": "利用另一種 MemoryMode 配置來模仿 Switch 開發模式。\n\n這僅對高解析度紋理套件或 4K 解析度模組有用。不會提高效能。\n\n如果不確定,請保持關閉狀態。", + "IgnoreMissingServicesTooltip": "忽略未實現的 Horizon OS 服務。這可能有助於在啟動某些遊戲時避免崩潰。\n\n如果不確定,請保持關閉狀態。", + "GraphicsBackendThreadingTooltip": "在第二個執行緒上執行圖形後端指令。\n\n在本身不支援多執行緒的 GPU 驅動程式上,可加快著色器編譯、減少卡頓並提高效能。在支援多執行緒的驅動程式上效能略有提升。\n\n如果不確定,請設定為自動。", + "GalThreadingTooltip": "在第二個執行緒上執行圖形後端指令。\n\n在本身不支援多執行緒的 GPU 驅動程式上,可加快著色器編譯、減少卡頓並提高效能。在支援多執行緒的驅動程式上效能略有提升。\n\n如果不確定,請設定為自動。", + "ShaderCacheToggleTooltip": "儲存磁碟著色器快取,減少後續執行時的卡頓。\n\n如果不確定,請保持開啟狀態。", + "ResolutionScaleTooltip": "使用倍數提升遊戲的繪製解析度。\n\n少數遊戲可能無法使用此功能,即使提高解析度也會顯得像素化;對於這些遊戲,您可能需要找到去除反鋸齒或提高內部繪製解析度的模組。對於後者,您可能需要選擇原生。\n\n此選項可在遊戲執行時透過點選下方的「套用」進行變更;您只需將設定視窗移到一旁,然後進行試驗,直到找到您喜歡的遊戲效果。\n\n請記住,4 倍幾乎對任何設定都是過度的。", + "ResolutionScaleEntryTooltip": "浮點解析度刻度,如 1.5。非整數刻度更容易出現問題或崩潰。", + "AnisotropyTooltip": "各向異性過濾等級。設定為自動可使用遊戲要求的值。", + "AspectRatioTooltip": "套用於繪製器視窗的長寬比。\n\n只有在遊戲中使用長寬比模組時才可變更,否則圖形會被拉伸。\n\n如果不確定,請保持 16:9 狀態。", + "ShaderDumpPathTooltip": "圖形著色器傾印路徑", + "FileLogTooltip": "將控制台日誌儲存到磁碟上的日誌檔案中。不會影響效能。", + "StubLogTooltip": "在控制台中輸出日誌訊息。不會影響效能。", + "InfoLogTooltip": "在控制台中輸出資訊日誌訊息。不會影響效能。", + "WarnLogTooltip": "在控制台中輸出警告日誌訊息。不會影響效能。", + "ErrorLogTooltip": "在控制台中輸出錯誤日誌訊息。不會影響效能。", + "TraceLogTooltip": "在控制台中輸出追蹤日誌訊息。不會影響效能。", + "GuestLogTooltip": "在控制台中輸出客體日誌訊息。不會影響效能。", + "FileAccessLogTooltip": "在控制台中輸出檔案存取日誌訊息。", + "FSAccessLogModeTooltip": "啟用檔案系統存取日誌輸出到控制台中。可能的模式為 0 到 3", + "DeveloperOptionTooltip": "謹慎使用", + "OpenGlLogLevel": "需要啟用適當的日誌等級", + "DebugLogTooltip": "在控制台中輸出偵錯日誌訊息。\n\n只有在人員特別指示的情況下才能使用,因為這會導致日誌難以閱讀,並降低模擬器效能。", + "LoadApplicationFileTooltip": "開啟檔案總管,選擇與 Switch 相容的檔案來載入", + "LoadApplicationFolderTooltip": "開啟檔案總管,選擇與 Switch 相容且解開封裝的應用程式來載入", + "OpenRyujinxFolderTooltip": "開啟 Ryujinx 檔案系統資料夾", + "OpenRyujinxLogsTooltip": "開啟日誌被寫入的資料夾", + "ExitTooltip": "結束 Ryujinx", "OpenSettingsTooltip": "開啟設定視窗", - "OpenProfileManagerTooltip": "開啟使用者帳戶管理視窗", - "StopEmulationTooltip": "停止執行目前遊戲並回到選擇界面", - "CheckUpdatesTooltip": "檢查 Ryujinx 新版本", + "OpenProfileManagerTooltip": "開啟使用者設定檔管理員視窗", + "StopEmulationTooltip": "停止模擬目前遊戲,返回遊戲選擇介面", + "CheckUpdatesTooltip": "檢查 Ryujinx 的更新", "OpenAboutTooltip": "開啟關於視窗", "GridSize": "網格尺寸", - "GridSizeTooltip": "調整網格模式的大小", - "SettingsTabSystemSystemLanguageBrazilianPortuguese": "巴西葡萄牙語", - "AboutRyujinxContributorsButtonHeader": "查看所有參與者", - "SettingsTabSystemAudioVolume": "音量:", + "GridSizeTooltip": "調整網格的大小", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "巴西葡萄牙文", + "AboutRyujinxContributorsButtonHeader": "查看所有貢獻者", + "SettingsTabSystemAudioVolume": "音量: ", "AudioVolumeTooltip": "調節音量", - "SettingsTabSystemEnableInternetAccess": "啟用網路連接", - "EnableInternetAccessTooltip": "開啟網路存取。此選項打開後,效果類似於 Switch 連接到網路的狀態。注意即使此選項關閉,應用程式偶爾也有可能連接到網路", - "GameListContextMenuManageCheatToolTip": "管理金手指", - "GameListContextMenuManageCheat": "管理金手指", - "ControllerSettingsStickRange": "範圍:", + "SettingsTabSystemEnableInternetAccess": "訪客網際網路存取/區域網路模式", + "EnableInternetAccessTooltip": "允許模擬應用程式連線網際網路。\n\n當啟用此功能且系統連線到同一接入點時,具有區域網路模式的遊戲可相互連線。這也包括真正的遊戲機。\n\n不允許連接 Nintendo 伺服器。可能會導致某些嘗試連線網際網路的遊戲崩潰。\n\n如果不確定,請保持關閉狀態。", + "GameListContextMenuManageCheatToolTip": "管理密技", + "GameListContextMenuManageCheat": "管理密技", + "GameListContextMenuManageModToolTip": "管理模組", + "GameListContextMenuManageMod": "管理模組", + "ControllerSettingsStickRange": "範圍:", "DialogStopEmulationTitle": "Ryujinx - 停止模擬", - "DialogStopEmulationMessage": "你確定要停止模擬嗎?", - "SettingsTabCpu": "處理器", + "DialogStopEmulationMessage": "您確定要停止模擬嗎?", + "SettingsTabCpu": "CPU", "SettingsTabAudio": "音訊", "SettingsTabNetwork": "網路", - "SettingsTabNetworkConnection": "網路連接", + "SettingsTabNetworkConnection": "網路連線", "SettingsTabCpuCache": "CPU 快取", "SettingsTabCpuMemory": "CPU 模式", "DialogUpdaterFlatpakNotSupportedMessage": "請透過 Flathub 更新 Ryujinx。", - "UpdaterDisabledWarningTitle": "更新已停用!", - "GameListContextMenuOpenSdModsDirectory": "開啟 Atmosphere 模組資料夾", - "GameListContextMenuOpenSdModsDirectoryToolTip": "開啟此遊戲額外的SD記憶卡Atmosphere模組資料夾. 有用於包裝在真實主機上的模組.\n", + "UpdaterDisabledWarningTitle": "更新已停用!", "ControllerSettingsRotate90": "順時針旋轉 90°", - "IconSize": "圖示尺寸", - "IconSizeTooltip": "更改遊戲圖示大小", + "IconSize": "圖示大小", + "IconSizeTooltip": "變更遊戲圖示的大小", "MenuBarOptionsShowConsole": "顯示控制台", - "ShaderCachePurgeError": "清除 {0} 著色器快取時出現錯誤: {1}", + "ShaderCachePurgeError": "在 {0} 清除著色器快取時出錯: {1}", "UserErrorNoKeys": "找不到金鑰", "UserErrorNoFirmware": "找不到韌體", "UserErrorFirmwareParsingFailed": "韌體解析錯誤", "UserErrorApplicationNotFound": "找不到應用程式", "UserErrorUnknown": "未知錯誤", "UserErrorUndefined": "未定義錯誤", - "UserErrorNoKeysDescription": "Ryujinx 找不到 『prod.keys』 檔案", - "UserErrorNoFirmwareDescription": "Ryujinx 找不到任何已安裝的韌體", - "UserErrorFirmwareParsingFailedDescription": "Ryujinx 無法解密選擇的韌體。這通常是由於金鑰過舊。", - "UserErrorApplicationNotFoundDescription": "Ryujinx 在選中路徑找不到有效的應用程式。", - "UserErrorUnknownDescription": "發生未知錯誤!", - "UserErrorUndefinedDescription": "發生了未定義錯誤!此類錯誤不應出現,請聯絡開發人員!", - "OpenSetupGuideMessage": "開啟設定教學", - "NoUpdate": "沒有新版本", - "TitleUpdateVersionLabel": "版本 {0} - {1}", - "RyujinxInfo": "Ryujinx - 訊息", + "UserErrorNoKeysDescription": "Ryujinx 無法找到您的「prod.keys」檔案", + "UserErrorNoFirmwareDescription": "Ryujinx 無法找到已安裝的任何韌體", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx 無法解析所提供的韌體。這通常是由於金鑰過時造成的。", + "UserErrorApplicationNotFoundDescription": "Ryujinx 無法在指定路徑下找到有效的應用程式。", + "UserErrorUnknownDescription": "發生未知錯誤!", + "UserErrorUndefinedDescription": "發生未定義錯誤! 這種情況不應該發生,請聯絡開發人員", + "OpenSetupGuideMessage": "開啟設定指南", + "NoUpdate": "沒有更新", + "TitleUpdateVersionLabel": "版本 {0}", + "RyujinxInfo": "Ryujinx - 資訊", "RyujinxConfirm": "Ryujinx - 確認", "FileDialogAllTypes": "全部類型", "Never": "從不", - "SwkbdMinCharacters": "至少應為 {0} 個字長", - "SwkbdMinRangeCharacters": "必須為 {0}-{1} 個字長", + "SwkbdMinCharacters": "長度必須至少為 {0} 個字元", + "SwkbdMinRangeCharacters": "長度必須為 {0} 到 {1} 個字元", "SoftwareKeyboard": "軟體鍵盤", - "SoftwareKeyboardModeNumbersOnly": "只接受數字", - "SoftwareKeyboardModeAlphabet": "不支援中日韓統一表意文字字元", - "SoftwareKeyboardModeASCII": "只接受 ASCII 符號", - "DialogControllerAppletMessagePlayerRange": "本遊戲需要 {0} 個玩家持有:\n\n類型:{1}\n\n玩家:{2}\n\n{3}請打開設定畫面並配置控制器,或者關閉本視窗。", - "DialogControllerAppletMessage": "本遊戲需要剛好 {0} 個玩家持有:\n\n類型:{1}\n\n玩家:{2}\n\n{3}請打開設定畫面並配置控制器,或者關閉本視窗。", - "DialogControllerAppletDockModeSet": "現在處於主機模式,無法使用掌機操作方式\n\n", + "SoftwareKeyboardModeNumeric": "必須是 0 到 9 或「.」", + "SoftwareKeyboardModeAlphabet": "必須是非中日韓字元 (non CJK)", + "SoftwareKeyboardModeASCII": "必須是 ASCII 文字", + "ControllerAppletControllers": "支援的控制器:", + "ControllerAppletPlayers": "玩家:", + "ControllerAppletDescription": "您目前的配置無效。開啟設定並重新配置輸入。", + "ControllerAppletDocked": "已設定底座模式。手提控制應該停用。", "UpdaterRenaming": "正在重新命名舊檔案...", - "UpdaterRenameFailed": "更新過程中無法重新命名檔案: {0}", - "UpdaterAddingFiles": "安裝更新中...", + "UpdaterRenameFailed": "更新程式無法重新命名檔案: {0}", + "UpdaterAddingFiles": "正在加入新檔案...", "UpdaterExtracting": "正在提取更新...", - "UpdaterDownloading": "下載新版本中...", + "UpdaterDownloading": "正在下載更新...", "Game": "遊戲", - "Docked": "主機模式", - "Handheld": "掌機模式", + "Docked": "底座模式", + "Handheld": "手提模式", "ConnectionError": "連接錯誤。", - "AboutPageDeveloperListMore": "{0} 等開發者...", - "ApiError": "API 錯誤", - "LoadingHeading": "正在啟動 {0}", - "CompilingPPTC": "編譯 PPTC 快取中", - "CompilingShaders": "編譯著色器中", + "AboutPageDeveloperListMore": "{0} 等人...", + "ApiError": "API 錯誤。", + "LoadingHeading": "正在載入 {0}", + "CompilingPPTC": "正在編譯 PTC", + "CompilingShaders": "正在編譯著色器", "AllKeyboards": "所有鍵盤", - "OpenFileDialogTitle": "選擇支援的檔案格式", - "OpenFolderDialogTitle": "選擇一個包含已解開封裝遊戲的資料夾\n", - "AllSupportedFormats": "全部支援的格式", + "OpenFileDialogTitle": "選取支援的檔案格式", + "OpenFolderDialogTitle": "選取解開封裝遊戲的資料夾", + "AllSupportedFormats": "所有支援的格式", "RyujinxUpdater": "Ryujinx 更新程式", - "SettingsTabHotkeys": "快捷鍵", + "SettingsTabHotkeys": "鍵盤快速鍵", "SettingsTabHotkeysHotkeys": "鍵盤快捷鍵", - "SettingsTabHotkeysToggleVsyncHotkey": "切換垂直同步:", - "SettingsTabHotkeysScreenshotHotkey": "截圖:", - "SettingsTabHotkeysShowUiHotkey": "隱藏使用者介面:", - "SettingsTabHotkeysPauseHotkey": "暫停:", - "SettingsTabHotkeysToggleMuteHotkey": "靜音:", - "ControllerMotionTitle": "體感操作設定", + "SettingsTabHotkeysToggleVsyncHotkey": "切換垂直同步:", + "SettingsTabHotkeysScreenshotHotkey": "擷取畫面:", + "SettingsTabHotkeysShowUiHotkey": "顯示 UI:", + "SettingsTabHotkeysPauseHotkey": "暫停:", + "SettingsTabHotkeysToggleMuteHotkey": "靜音:", + "ControllerMotionTitle": "體感控制設定", "ControllerRumbleTitle": "震動設定", - "SettingsSelectThemeFileDialogTitle": "選擇主題檔案", - "SettingsXamlThemeFile": "Xaml 主題檔案", - "AvatarWindowTitle": "管理帳號 - 頭貼", + "SettingsSelectThemeFileDialogTitle": "選取佈景主題檔案", + "SettingsXamlThemeFile": "Xaml 佈景主題檔案", + "AvatarWindowTitle": "管理帳戶 - 大頭貼", "Amiibo": "Amiibo", "Unknown": "未知", "Usage": "用途", "Writable": "可寫入", - "SelectDlcDialogTitle": "選擇 DLC 檔案", - "SelectUpdateDialogTitle": "選擇更新檔", - "UserProfileWindowTitle": "管理使用者帳戶", - "CheatWindowTitle": "管理遊戲金手指", - "DlcWindowTitle": "管理遊戲 DLC", - "UpdateWindowTitle": "管理遊戲更新", - "CheatWindowHeading": "金手指可用於 {0} [{1}]", - "BuildId": "版本編號:", - "DlcWindowHeading": "DLC 可用於 {0} [{1}]", + "SelectDlcDialogTitle": "選取 DLC 檔案", + "SelectUpdateDialogTitle": "選取更新檔", + "SelectModDialogTitle": "選取模組資料夾", + "UserProfileWindowTitle": "使用者設定檔管理員", + "CheatWindowTitle": "密技管理員", + "DlcWindowTitle": "管理 {0} 的可下載內容 ({1})", + "UpdateWindowTitle": "遊戲更新管理員", + "CheatWindowHeading": "可用於 {0} [{1}] 的密技", + "BuildId": "組建識別碼:", + "DlcWindowHeading": "{0} 個可下載內容", + "ModWindowHeading": "{0} 模組", "UserProfilesEditProfile": "編輯所選", "Cancel": "取消", "Save": "儲存", - "Discard": "放棄更改", - "UserProfilesSetProfileImage": "設定帳戶頭像", - "UserProfileEmptyNameError": "使用者名稱為必填", - "UserProfileNoImageError": "必須設定帳戶頭像", - "GameUpdateWindowHeading": "更新可用於 {0} [{1}]", - "SettingsTabHotkeysResScaleUpHotkey": "提高解析度:", - "SettingsTabHotkeysResScaleDownHotkey": "降低解析度:", - "UserProfilesName": "使用者名稱:", - "UserProfilesUserId": "使用者 ID:", - "SettingsTabGraphicsBackend": "圖像處理後台架構", - "SettingsTabGraphicsBackendTooltip": "用來圖像處理的後台架構", + "Discard": "放棄變更", + "Paused": "暫停", + "UserProfilesSetProfileImage": "設定設定檔圖像", + "UserProfileEmptyNameError": "名稱為必填", + "UserProfileNoImageError": "必須設定設定檔圖像", + "GameUpdateWindowHeading": "可用於 {0} ({1}) 的更新", + "SettingsTabHotkeysResScaleUpHotkey": "提高解析度:", + "SettingsTabHotkeysResScaleDownHotkey": "降低解析度:", + "UserProfilesName": "名稱:", + "UserProfilesUserId": "使用者 ID:", + "SettingsTabGraphicsBackend": "圖形後端", + "SettingsTabGraphicsBackendTooltip": "選擇模擬器將使用的圖形後端。\n\n只要驅動程式是最新的,Vulkan 對所有現代顯示卡來說都更好用。Vulkan 還能在所有 GPU 廠商上實現更快的著色器編譯 (減少卡頓)。\n\nOpenGL 在舊式 Nvidia GPU、Linux 上的舊式 AMD GPU 或 VRAM 較低的 GPU 上可能會取得更好的效果,不過著色器編譯的卡頓會更嚴重。\n\n如果不確定,請設定為 Vulkan。如果您的 GPU 使用最新的圖形驅動程式也不支援 Vulkan,請設定為 OpenGL。", "SettingsEnableTextureRecompression": "開啟材質重新壓縮", - "SettingsEnableTextureRecompressionTooltip": "壓縮某些材質以減少 VRAM 使用。\n\n推薦用於小於 4GiB VRAM 的 GPU。\n\n如果不確定請關閉本功能。", - "SettingsTabGraphicsPreferredGpu": "優先選取的 GPU", - "SettingsTabGraphicsPreferredGpuTooltip": "選擇支持運行Vulkan圖像處理架構的GPU.\n\n此設定不會影響GPU運行OpenGL.\n\n如果不確定, 請設定標籤為\"dGPU\"的GPU. 如果沒有, 請保留原始設定.", - "SettingsAppRequiredRestartMessage": "必須重啟 Ryujinx", - "SettingsGpuBackendRestartMessage": "圖像處理後台架構或GPU相關設定已被修改。需要重新啟動才能套用。", - "SettingsGpuBackendRestartSubMessage": "你確定要現在重新啟動嗎?", - "RyujinxUpdaterMessage": "你確定要將 Ryujinx 更新到最新版本嗎?", - "SettingsTabHotkeysVolumeUpHotkey": "增加音量:", - "SettingsTabHotkeysVolumeDownHotkey": "降低音量:", + "SettingsEnableTextureRecompressionTooltip": "壓縮 ASTC 紋理,以減少 VRAM 占用。\n\n使用這種紋理格式的遊戲包括 Astral Chain、Bayonetta 3、Fire Emblem Engage、Metroid Prime Remastered、Super Mario Bros. Wonder 和 The Legend of Zelda: Tears of the Kingdom。\n\n使用 4GB 或更低 VRAM 的顯示卡在執行這些遊戲時可能會崩潰。\n\n只有在上述遊戲的 VRAM 即將耗盡時才啟用。如果不確定,請保持關閉狀態。", + "SettingsTabGraphicsPreferredGpu": "首選 GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "選擇將與 Vulkan 圖形後端一起使用的顯示卡。\n\n不會影響 OpenGL 將使用的 GPU。\n\n如果不確定,請設定為標記為「dGPU」的 GPU。如果沒有,則保持原狀。", + "SettingsAppRequiredRestartMessage": "需要重新啟動 Ryujinx", + "SettingsGpuBackendRestartMessage": "圖形後端或 GPU 設定已修改。這需要重新啟動才能套用。", + "SettingsGpuBackendRestartSubMessage": "您現在要重新啟動嗎?", + "RyujinxUpdaterMessage": "您想將 Ryujinx 升級到最新版本嗎?", + "SettingsTabHotkeysVolumeUpHotkey": "提高音量:", + "SettingsTabHotkeysVolumeDownHotkey": "降低音量:", "SettingsEnableMacroHLE": "啟用 Macro HLE", - "SettingsEnableMacroHLETooltip": "GPU 微代碼的高階模擬。\n\n可以提升效能,但可能會導致某些遊戲出現圖形顯示故障。\n\n如果不確定請設定為\"開啟\"。", + "SettingsEnableMacroHLETooltip": "GPU 巨集程式碼的進階模擬。\n\n可提高效能,但在某些遊戲中可能會導致圖形閃爍。\n\n如果不確定,請保持開啟狀態。", "SettingsEnableColorSpacePassthrough": "色彩空間直通", - "SettingsEnableColorSpacePassthroughTooltip": "指揮Vulkan後端直通色彩資訊而不需指定色彩空間,對於廣色域顯示的使用者,這會造成較多抖色,犧牲色彩正確性。", + "SettingsEnableColorSpacePassthroughTooltip": "指示 Vulkan 後端在不指定色彩空間的情況下傳遞色彩資訊。對於使用廣色域顯示器的使用者來說,這可能會帶來更鮮艷的色彩,但代價是犧牲色彩的正確性。", "VolumeShort": "音量", - "UserProfilesManageSaves": "管理遊戲存檔", - "DeleteUserSave": "你確定要刪除此遊戲的存檔嗎?", - "IrreversibleActionNote": "本動作將無法挽回。", - "SaveManagerHeading": "管理 {0} 的遊戲存檔", - "SaveManagerTitle": "遊戲存檔管理器", + "UserProfilesManageSaves": "管理存檔", + "DeleteUserSave": "您想刪除此遊戲的使用者存檔嗎?", + "IrreversibleActionNote": "此動作將無法復原。", + "SaveManagerHeading": "管理 {0} 的存檔", + "SaveManagerTitle": "存檔管理員", "Name": "名稱", "Size": "大小", "Search": "搜尋", - "UserProfilesRecoverLostAccounts": "恢復遺失的帳戶", - "Recover": "恢復", - "UserProfilesRecoverHeading": "在以下帳戶找到了一些遊戲存檔", - "UserProfilesRecoverEmptyList": "沒有可恢復的使用者帳戶", - "GraphicsAATooltip": "在遊戲繪圖上套用抗鋸齒", - "GraphicsAALabel": "抗鋸齒:", - "GraphicsScalingFilterLabel": "縮放過濾器:", - "GraphicsScalingFilterTooltip": "啟用畫幀緩衝區縮放", - "GraphicsScalingFilterLevelLabel": "記錄檔等級", - "GraphicsScalingFilterLevelTooltip": "設定縮放過濾器的強度", + "UserProfilesRecoverLostAccounts": "復原遺失的帳戶", + "Recover": "復原", + "UserProfilesRecoverHeading": "發現下列帳戶有一些存檔", + "UserProfilesRecoverEmptyList": "無設定檔可復原", + "GraphicsAATooltip": "對遊戲繪製進行反鋸齒處理。\n\nFXAA 會模糊大部分圖像,而 SMAA 則會嘗試找出鋸齒邊緣並將其平滑化。\n\n不建議與 FSR 縮放濾鏡一起使用。\n\n此選項可在遊戲執行時透過點選下方的「套用」進行變更;您只需將設定視窗移到一旁,然後進行試驗,直到找到您喜歡的遊戲效果。\n\n如果不確定,請選擇無狀態。", + "GraphicsAALabel": "反鋸齒:", + "GraphicsScalingFilterLabel": "縮放過濾器:", + "GraphicsScalingFilterTooltip": "選擇使用解析度縮放時套用的縮放過濾器。\n\nBilinear (雙線性) 濾鏡適用於 3D 遊戲,是一個安全的預設選項。\n\n建議像素美術遊戲使用 Nearest (最近性) 濾鏡。\n\nFSR 1.0 只是一個銳化濾鏡,不建議與 FXAA 或 SMAA 一起使用。\n\n此選項可在遊戲執行時透過點選下方的「套用」進行變更;您只需將設定視窗移到一旁,然後進行試驗,直到找到您喜歡的遊戲效果。\n\n如果不確定,請保持 Bilinear (雙線) 狀態。", + "GraphicsScalingFilterLevelLabel": "日誌等級", + "GraphicsScalingFilterLevelTooltip": "設定 FSR 1.0 銳化等級。越高越清晰。", "SmaaLow": "低階 SMAA", "SmaaMedium": "中階 SMAA", "SmaaHigh": "高階 SMAA", "SmaaUltra": "超高階 SMAA", "UserEditorTitle": "編輯使用者", "UserEditorTitleCreate": "建立使用者", - "SettingsTabNetworkInterface": "網路介面:", - "NetworkInterfaceTooltip": "用於具有 LAN 功能的網路介面", + "SettingsTabNetworkInterface": "網路介面:", + "NetworkInterfaceTooltip": "用於 LAN/LDN 功能的網路介面。\n\n與 VPN 或 XLink Kai 以及支援區域網路的遊戲配合使用,可用於在網路上偽造同網際網路連線。\n\n如果不確定,請保持預設狀態。", "NetworkInterfaceDefault": "預設", "PackagingShaders": "著色器封裝", - "AboutChangelogButton": "在 GitHub 查看更新日誌", - "AboutChangelogButtonTooltipMessage": "在瀏覽器中打開此Ryujinx版本的更新日誌。" -} \ No newline at end of file + "AboutChangelogButton": "在 GitHub 上檢視更新日誌", + "AboutChangelogButtonTooltipMessage": "在預設瀏覽器中開啟此版本的更新日誌。", + "SettingsTabNetworkMultiplayer": "多人遊戲", + "MultiplayerMode": "模式:", + "MultiplayerModeTooltip": "變更 LDN 多人遊戲模式。\n\nLdnMitm 將修改遊戲中的本機無線/本機遊戲功能,使其如同區域網路一樣執行,允許與其他安裝了 ldn_mitm 模組的 Ryujinx 實例和已破解的 Nintendo Switch 遊戲機進行本機同網路連線。\n\n多人遊戲要求所有玩家使用相同的遊戲版本 (例如,Super Smash Bros. Ultimate v13.0.1 無法連接 v13.0.0)。\n\n如果不確定,請保持 Disabled (停用) 狀態。" +} diff --git a/src/Ryujinx/Common/Locale/LocaleManager.cs b/src/Ryujinx/Common/Locale/LocaleManager.cs index 2d3deeaa38..257611e65a 100644 --- a/src/Ryujinx/Common/Locale/LocaleManager.cs +++ b/src/Ryujinx/Common/Locale/LocaleManager.cs @@ -104,7 +104,7 @@ namespace Ryujinx.Ava.Common.Locale { return _localeLanguageCode switch { - "he_IL" => true, + "ar_SA" or "he_IL" => true, _ => false }; } diff --git a/src/Ryujinx/Ryujinx.csproj b/src/Ryujinx/Ryujinx.csproj index 2a5a9fadd6..38453f2c66 100644 --- a/src/Ryujinx/Ryujinx.csproj +++ b/src/Ryujinx/Ryujinx.csproj @@ -1,4 +1,4 @@ - + net8.0 win-x64;osx-x64;linux-x64 @@ -110,6 +110,7 @@ + @@ -122,6 +123,7 @@ + @@ -135,6 +137,7 @@ + @@ -147,6 +150,7 @@ + From 20a280525f10c4c6a0a2486ddf33b6d04946d1b9 Mon Sep 17 00:00:00 2001 From: WilliamWsyHK Date: Fri, 22 Mar 2024 03:08:25 +0800 Subject: [PATCH 13/87] [UI] Fix Display Name Translations & Update some Chinese Translations (#6388) * Remove incorrect additional back-slash * Touch-up on TChinese translations * Little touch-up on SChinese translations --- src/Ryujinx/Assets/Locales/ar_SA.json | 2 +- src/Ryujinx/Assets/Locales/en_US.json | 2 +- src/Ryujinx/Assets/Locales/fr_FR.json | 2 +- src/Ryujinx/Assets/Locales/he_IL.json | 2 +- src/Ryujinx/Assets/Locales/ja_JP.json | 2 +- src/Ryujinx/Assets/Locales/ko_KR.json | 2 +- src/Ryujinx/Assets/Locales/th_TH.json | 2 +- src/Ryujinx/Assets/Locales/uk_UA.json | 2 +- src/Ryujinx/Assets/Locales/zh_CN.json | 2 +- src/Ryujinx/Assets/Locales/zh_TW.json | 26 +++++++++++++------------- 10 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/Ryujinx/Assets/Locales/ar_SA.json b/src/Ryujinx/Assets/Locales/ar_SA.json index 1fac4850c7..57ca5b0aea 100644 --- a/src/Ryujinx/Assets/Locales/ar_SA.json +++ b/src/Ryujinx/Assets/Locales/ar_SA.json @@ -341,7 +341,7 @@ "DialogThemeRestartMessage": "تم حفظ السمة. إعادة التشغيل مطلوبة لتطبيق السمة.", "DialogThemeRestartSubMessage": "هل تريد إعادة التشغيل", "DialogFirmwareInstallEmbeddedMessage": "هل ترغب في تثبيت البرنامج الثابت المدمج في هذه اللعبة؟ (البرنامج الثابت {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "لم يتم العثور على أي برنامج ثابت مثبت ولكن Ryujinx كان قادراً على تثبيت البرنامج الثابت {0} من اللعبة المقدمة.\\nسيبدأ المحاكي الآن.", + "DialogFirmwareInstallEmbeddedSuccessMessage": "لم يتم العثور على أي برنامج ثابت مثبت ولكن Ryujinx كان قادراً على تثبيت البرنامج الثابت {0} من اللعبة المقدمة.\nسيبدأ المحاكي الآن.", "DialogFirmwareNoFirmwareInstalledMessage": "لا يوجد برنامج ثابت مثبت", "DialogFirmwareInstalledMessage": "تم تثبيت البرنامج الثابت {0}", "DialogInstallFileTypesSuccessMessage": "تم تثبيت أنواع الملفات بنجاح!", diff --git a/src/Ryujinx/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json index 2febf90ec3..efc4525c09 100644 --- a/src/Ryujinx/Assets/Locales/en_US.json +++ b/src/Ryujinx/Assets/Locales/en_US.json @@ -341,7 +341,7 @@ "DialogThemeRestartMessage": "Theme has been saved. A restart is needed to apply the theme.", "DialogThemeRestartSubMessage": "Do you want to restart", "DialogFirmwareInstallEmbeddedMessage": "Would you like to install the firmware embedded in this game? (Firmware {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "No installed firmware was found but Ryujinx was able to install firmware {0} from the provided game.\\nThe emulator will now start.", + "DialogFirmwareInstallEmbeddedSuccessMessage": "No installed firmware was found but Ryujinx was able to install firmware {0} from the provided game.\nThe emulator will now start.", "DialogFirmwareNoFirmwareInstalledMessage": "No Firmware Installed", "DialogFirmwareInstalledMessage": "Firmware {0} was installed", "DialogInstallFileTypesSuccessMessage": "Successfully installed file types!", diff --git a/src/Ryujinx/Assets/Locales/fr_FR.json b/src/Ryujinx/Assets/Locales/fr_FR.json index 1c84573c27..b1501d848d 100644 --- a/src/Ryujinx/Assets/Locales/fr_FR.json +++ b/src/Ryujinx/Assets/Locales/fr_FR.json @@ -341,7 +341,7 @@ "DialogThemeRestartMessage": "Le thème a été enregistré. Un redémarrage est requis pour appliquer le thème.", "DialogThemeRestartSubMessage": "Voulez-vous redémarrer", "DialogFirmwareInstallEmbeddedMessage": "Voulez-vous installer le firmware intégré dans ce jeu ? (Firmware {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "Aucun firmware installé n'a été trouvé mais Ryujinx a pu installer le firmware {0} à partir du jeu fourni.\\nL'émulateur va maintenant démarrer.", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Aucun firmware installé n'a été trouvé mais Ryujinx a pu installer le firmware {0} à partir du jeu fourni.\nL'émulateur va maintenant démarrer.", "DialogFirmwareNoFirmwareInstalledMessage": "Aucun Firmware installé", "DialogFirmwareInstalledMessage": "Le firmware {0} a été installé", "DialogInstallFileTypesSuccessMessage": "Types de fichiers installés avec succès!", diff --git a/src/Ryujinx/Assets/Locales/he_IL.json b/src/Ryujinx/Assets/Locales/he_IL.json index 5ba56c2691..afa92f8d29 100644 --- a/src/Ryujinx/Assets/Locales/he_IL.json +++ b/src/Ryujinx/Assets/Locales/he_IL.json @@ -341,7 +341,7 @@ "DialogThemeRestartMessage": "ערכת הנושא נשמרה. יש צורך בהפעלה מחדש כדי להחיל את ערכת הנושא.", "DialogThemeRestartSubMessage": "האם ברצונך להפעיל מחדש?", "DialogFirmwareInstallEmbeddedMessage": "האם תרצו להתקין את הקושחה המוטמעת במשחק הזה? (קושחה {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "לא נמצאה קושחה מותקנת אבל ריוג'ינקס הצליח להתקין קושחה {0} מהמשחק שסופק. \\nהאמולטור יופעל כעת.", + "DialogFirmwareInstallEmbeddedSuccessMessage": "לא נמצאה קושחה מותקנת אבל ריוג'ינקס הצליח להתקין קושחה {0} מהמשחק שסופק. \nהאמולטור יופעל כעת.", "DialogFirmwareNoFirmwareInstalledMessage": "לא מותקנת קושחה", "DialogFirmwareInstalledMessage": "הקושחה {0} הותקנה", "DialogInstallFileTypesSuccessMessage": "סוגי קבצים הותקנו בהצלחה!", diff --git a/src/Ryujinx/Assets/Locales/ja_JP.json b/src/Ryujinx/Assets/Locales/ja_JP.json index ce4f07c415..b57d15e2ee 100644 --- a/src/Ryujinx/Assets/Locales/ja_JP.json +++ b/src/Ryujinx/Assets/Locales/ja_JP.json @@ -341,7 +341,7 @@ "DialogThemeRestartMessage": "テーマがセーブされました. テーマを適用するには再起動が必要です.", "DialogThemeRestartSubMessage": "再起動しますか", "DialogFirmwareInstallEmbeddedMessage": "このゲームに含まれるファームウェアをインストールしてよろしいですか? (ファームウェア {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "ファームウェアがインストールされていませんが, ゲームに含まれるファームウェア {0} をインストールできます.\\nエミュレータが開始します.", + "DialogFirmwareInstallEmbeddedSuccessMessage": "ファームウェアがインストールされていませんが, ゲームに含まれるファームウェア {0} をインストールできます.\nエミュレータが開始します.", "DialogFirmwareNoFirmwareInstalledMessage": "ファームウェアがインストールされていません", "DialogFirmwareInstalledMessage": "ファームウェア {0} がインストールされました", "DialogInstallFileTypesSuccessMessage": "ファイル形式のインストールに成功しました!", diff --git a/src/Ryujinx/Assets/Locales/ko_KR.json b/src/Ryujinx/Assets/Locales/ko_KR.json index 1db5215e57..133a51b630 100644 --- a/src/Ryujinx/Assets/Locales/ko_KR.json +++ b/src/Ryujinx/Assets/Locales/ko_KR.json @@ -341,7 +341,7 @@ "DialogThemeRestartMessage": "테마가 저장되었습니다. 테마를 적용하려면 다시 시작해야 합니다.", "DialogThemeRestartSubMessage": "다시 시작하겠습니까?", "DialogFirmwareInstallEmbeddedMessage": "이 게임에 내장된 펌웨어를 설치하겠습니까? (펌웨어 {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "설치된 펌웨어가 없지만 Ryujinx가 제공된 게임에서 펌웨어 {0}을(를) 설치할 수 있었습니다.\\n이제 에뮬레이터가 시작됩니다.", + "DialogFirmwareInstallEmbeddedSuccessMessage": "설치된 펌웨어가 없지만 Ryujinx가 제공된 게임에서 펌웨어 {0}을(를) 설치할 수 있었습니다.\n이제 에뮬레이터가 시작됩니다.", "DialogFirmwareNoFirmwareInstalledMessage": "설치된 펌웨어 없음", "DialogFirmwareInstalledMessage": "펌웨어 {0}이(가) 설치됨", "DialogInstallFileTypesSuccessMessage": "파일 형식을 성공적으로 설치했습니다!", diff --git a/src/Ryujinx/Assets/Locales/th_TH.json b/src/Ryujinx/Assets/Locales/th_TH.json index 61f50c7c2e..e2132f1a45 100644 --- a/src/Ryujinx/Assets/Locales/th_TH.json +++ b/src/Ryujinx/Assets/Locales/th_TH.json @@ -341,7 +341,7 @@ "DialogThemeRestartMessage": "บันทึกธีมแล้ว จำเป็นต้องรีสตาร์ทเพื่อใช้ธีม", "DialogThemeRestartSubMessage": "คุณต้องการรีสตาร์ทหรือไม่?", "DialogFirmwareInstallEmbeddedMessage": "คุณต้องการติดตั้งเฟิร์มแวร์ที่ฝังอยู่ในเกมนี้หรือไม่? (เฟิร์มแวร์ {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "ไม่พบเฟิร์มแวร์ที่ติดตั้งไว้ แต่ รียูจินซ์ สามารถติดตั้งเฟิร์มแวร์ได้ {0} จากเกมที่ให้มา\\nตอนนี้โปรแกรมจำลองจะเริ่มทำงาน", + "DialogFirmwareInstallEmbeddedSuccessMessage": "ไม่พบเฟิร์มแวร์ที่ติดตั้งไว้ แต่ รียูจินซ์ สามารถติดตั้งเฟิร์มแวร์ได้ {0} จากเกมที่ให้มา\nตอนนี้โปรแกรมจำลองจะเริ่มทำงาน", "DialogFirmwareNoFirmwareInstalledMessage": "ไม่มีการติดตั้งเฟิร์มแวร์", "DialogFirmwareInstalledMessage": "เฟิร์มแวร์ทำการติดตั้งแล้ว {0}", "DialogInstallFileTypesSuccessMessage": "ติดตั้งประเภทไฟล์สำเร็จแล้ว!", diff --git a/src/Ryujinx/Assets/Locales/uk_UA.json b/src/Ryujinx/Assets/Locales/uk_UA.json index 61af5c32a5..a77135dca4 100644 --- a/src/Ryujinx/Assets/Locales/uk_UA.json +++ b/src/Ryujinx/Assets/Locales/uk_UA.json @@ -341,7 +341,7 @@ "DialogThemeRestartMessage": "Тему збережено. Щоб застосувати тему, потрібен перезапуск.", "DialogThemeRestartSubMessage": "Ви хочете перезапустити", "DialogFirmwareInstallEmbeddedMessage": "Бажаєте встановити прошивку, вбудовану в цю гру? (Прошивка {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "Встановлену прошивку не знайдено, але Ryujinx вдалося встановити прошивку {0} з наданої гри.\\nТепер запуститься емулятор.", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Встановлену прошивку не знайдено, але Ryujinx вдалося встановити прошивку {0} з наданої гри.\nТепер запуститься емулятор.", "DialogFirmwareNoFirmwareInstalledMessage": "Прошивка не встановлена", "DialogFirmwareInstalledMessage": "Встановлено прошивку {0}", "DialogInstallFileTypesSuccessMessage": "Успішно встановлено типи файлів!", diff --git a/src/Ryujinx/Assets/Locales/zh_CN.json b/src/Ryujinx/Assets/Locales/zh_CN.json index ce07385c1f..fcd76f50d8 100644 --- a/src/Ryujinx/Assets/Locales/zh_CN.json +++ b/src/Ryujinx/Assets/Locales/zh_CN.json @@ -412,7 +412,7 @@ "MenuBarOptionsResumeEmulation": "继续", "AboutUrlTooltipMessage": "在浏览器中打开 Ryujinx 模拟器官网。", "AboutDisclaimerMessage": "Ryujinx 与 Nintendo™ 以及其合作伙伴没有任何关联。", - "AboutAmiiboDisclaimerMessage": "我们的 Amiibo 模拟使用了\nAmiiboAPI (www.amiiboapi.com) ", + "AboutAmiiboDisclaimerMessage": "我们的 Amiibo 模拟使用了\nAmiiboAPI (www.amiiboapi.com)。", "AboutPatreonUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 Patreon 赞助页。", "AboutGithubUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 GitHub 代码库。", "AboutDiscordUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 Discord 邀请链接。", diff --git a/src/Ryujinx/Assets/Locales/zh_TW.json b/src/Ryujinx/Assets/Locales/zh_TW.json index d9df134f73..4d3a78d1aa 100644 --- a/src/Ryujinx/Assets/Locales/zh_TW.json +++ b/src/Ryujinx/Assets/Locales/zh_TW.json @@ -127,8 +127,8 @@ "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "美洲西班牙文", "SettingsTabSystemSystemLanguageSimplifiedChinese": "簡體中文", "SettingsTabSystemSystemLanguageTraditionalChinese": "正體中文 (建議)", - "SettingsTabSystemSystemTimeZone": "系統時區:", - "SettingsTabSystemSystemTime": "系統時鐘:", + "SettingsTabSystemSystemTimeZone": "系統時區:", + "SettingsTabSystemSystemTime": "系統時鐘:", "SettingsTabSystemEnableVsync": "垂直同步", "SettingsTabSystemEnablePptc": "PPTC (剖析式持久轉譯快取, Profiled Persistent Translation Cache)", "SettingsTabSystemEnableFsIntegrityChecks": "檔案系統完整性檢查", @@ -138,9 +138,9 @@ "SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSDL2": "SDL2", "SettingsTabSystemHacks": "補釘修正", - "SettingsTabSystemHacksNote": "可能導致不穩定", - "SettingsTabSystemExpandDramSize": "使用其他記憶體配置 (開發者)", - "SettingsTabSystemIgnoreMissingServices": "忽略遺失的服務", + "SettingsTabSystemHacksNote": "可能導致模擬器不穩定", + "SettingsTabSystemExpandDramSize": "使用替代的記憶體配置 (開發者專用)", + "SettingsTabSystemIgnoreMissingServices": "忽略缺少的模擬器功能", "SettingsTabGraphics": "圖形", "SettingsTabGraphicsAPI": "圖形 API", "SettingsTabGraphicsEnableShaderCache": "啟用著色器快取", @@ -204,7 +204,7 @@ "ControllerSettingsHandheld": "手提模式", "ControllerSettingsInputDevice": "輸入裝置", "ControllerSettingsRefresh": "重新整理", - "ControllerSettingsDeviceDisabled": "停用", + "ControllerSettingsDeviceDisabled": "已停用", "ControllerSettingsControllerType": "控制器類型", "ControllerSettingsControllerTypeHandheld": "手提模式", "ControllerSettingsControllerTypeProController": "Pro 控制器", @@ -341,7 +341,7 @@ "DialogThemeRestartMessage": "佈景主題設定已儲存。需要重新啟動才能套用主題。", "DialogThemeRestartSubMessage": "您要重新啟動嗎", "DialogFirmwareInstallEmbeddedMessage": "您想安裝遊戲內建的韌體嗎? (韌體 {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "未找到已安裝的韌體,但 Ryujinx 可以從現有的遊戲安裝韌體{0}.\\n模擬器現在可以執行。", + "DialogFirmwareInstallEmbeddedSuccessMessage": "未找到已安裝的韌體,但 Ryujinx 可以從現有的遊戲安裝韌體{0}。\n模擬器現在可以執行。", "DialogFirmwareNoFirmwareInstalledMessage": "未安裝韌體", "DialogFirmwareInstalledMessage": "已安裝韌體{0}", "DialogInstallFileTypesSuccessMessage": "成功安裝檔案類型!", @@ -398,8 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "指定檔案不包含所選遊戲的更新!", "DialogSettingsBackendThreadingWarningTitle": "警告 - 後端執行緒處理中", "DialogSettingsBackendThreadingWarningMessage": "變更此選項後,必須重新啟動 Ryujinx 才能完全生效。使用 Ryujinx 的多執行緒功能時,可能需要手動停用驅動程式本身的多執行緒功能,這取決於您的平台。", - "DialogModManagerDeletionWarningMessage": "您將刪除模組: {0}\n\n您確定要繼續嗎?", - "DialogModManagerDeletionAllWarningMessage": "您即將刪除此遊戲的所有模組。\n\n您確定要繼續嗎?", + "DialogModManagerDeletionWarningMessage": "您將刪除模組: {0}\n\n您確定要繼續嗎?", + "DialogModManagerDeletionAllWarningMessage": "您即將刪除此遊戲的所有模組。\n\n您確定要繼續嗎?", "SettingsTabGraphicsFeaturesOptions": "功能", "SettingsTabGraphicsBackendMultithreading": "圖形後端多執行緒:", "CommonAuto": "自動", @@ -418,7 +418,7 @@ "AboutDiscordUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 的 Discord 邀請連結。", "AboutTwitterUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 的 Twitter 網頁。", "AboutRyujinxAboutTitle": "關於:", - "AboutRyujinxAboutContent": "Ryujinx 是一款 Nintendo Switch™ 模擬器。\n請在 Patreon 上支持我們。\n關注我們的 Twitter 或 Discord 取得所有最新消息。\n對於有興趣貢獻的開發者,可以在我們的 GitHub 或 Discord 上了解更多資訊。", + "AboutRyujinxAboutContent": "Ryujinx 是一款 Nintendo Switch™ 模擬器。\n請在 Patreon 上支持我們。\n關注我們的 Twitter 或 Discord 取得所有最新消息。\n對於有興趣貢獻的開發者,可以在我們的 GitHub 或 Discord 上了解更多資訊。", "AboutRyujinxMaintainersTitle": "維護者:", "AboutRyujinxMaintainersContentTooltipMessage": "在預設瀏覽器中開啟貢獻者的網頁", "AboutRyujinxSupprtersTitle": "Patreon 支持者:", @@ -427,7 +427,7 @@ "AmiiboScanButtonLabel": "掃描", "AmiiboOptionsShowAllLabel": "顯示所有 Amiibo", "AmiiboOptionsUsRandomTagLabel": "補釘修正: 使用隨機標記的 Uuid", - "DlcManagerTableHeadingEnabledLabel": "啟用", + "DlcManagerTableHeadingEnabledLabel": "已啟用", "DlcManagerTableHeadingTitleIdLabel": "遊戲 ID", "DlcManagerTableHeadingContainerPathLabel": "容器路徑", "DlcManagerTableHeadingFullPathLabel": "完整路徑", @@ -538,7 +538,7 @@ "UserErrorFirmwareParsingFailedDescription": "Ryujinx 無法解析所提供的韌體。這通常是由於金鑰過時造成的。", "UserErrorApplicationNotFoundDescription": "Ryujinx 無法在指定路徑下找到有效的應用程式。", "UserErrorUnknownDescription": "發生未知錯誤!", - "UserErrorUndefinedDescription": "發生未定義錯誤! 這種情況不應該發生,請聯絡開發人員", + "UserErrorUndefinedDescription": "發生未定義錯誤! 這種情況不應該發生,請聯絡開發人員!", "OpenSetupGuideMessage": "開啟設定指南", "NoUpdate": "沒有更新", "TitleUpdateVersionLabel": "版本 {0}", @@ -550,7 +550,7 @@ "SwkbdMinRangeCharacters": "長度必須為 {0} 到 {1} 個字元", "SoftwareKeyboard": "軟體鍵盤", "SoftwareKeyboardModeNumeric": "必須是 0 到 9 或「.」", - "SoftwareKeyboardModeAlphabet": "必須是非中日韓字元 (non CJK)", + "SoftwareKeyboardModeAlphabet": "必須是非「中日韓字元」 (non CJK)", "SoftwareKeyboardModeASCII": "必須是 ASCII 文字", "ControllerAppletControllers": "支援的控制器:", "ControllerAppletPlayers": "玩家:", From c94a73ec60f3f75b36179cbc93d046701ed96253 Mon Sep 17 00:00:00 2001 From: Matt Heins Date: Thu, 21 Mar 2024 20:44:11 -0400 Subject: [PATCH 14/87] Updates the default value for BufferedQuery (#6351) AMD GPUs (possibly just RDNA 3) could hang with the previous value until the MaxQueryRetries was hit. Fix #6056 Co-authored-by: riperiperi --- src/Ryujinx.Graphics.Vulkan/Queries/BufferedQuery.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx.Graphics.Vulkan/Queries/BufferedQuery.cs b/src/Ryujinx.Graphics.Vulkan/Queries/BufferedQuery.cs index 3fdc5afa5f..714cb2833c 100644 --- a/src/Ryujinx.Graphics.Vulkan/Queries/BufferedQuery.cs +++ b/src/Ryujinx.Graphics.Vulkan/Queries/BufferedQuery.cs @@ -10,8 +10,8 @@ namespace Ryujinx.Graphics.Vulkan.Queries class BufferedQuery : IDisposable { private const int MaxQueryRetries = 5000; - private const long DefaultValue = -1; - private const long DefaultValueInt = 0xFFFFFFFF; + private const long DefaultValue = unchecked((long)0xFFFFFFFEFFFFFFFE); + private const long DefaultValueInt = 0xFFFFFFFE; private const ulong HighMask = 0xFFFFFFFF00000000; private readonly Vk _api; From dbfe859ed717c82d87d6d497fe1d647278f19284 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Sat, 23 Mar 2024 16:31:54 -0300 Subject: [PATCH 15/87] Add a few missing locale strings on Avalonia (#6556) * Add a few missing locale strings on Avalonia * Rename LDN MitM to ldn_mitm --- src/Ryujinx/Assets/Locales/en_US.json | 7 ++++++- src/Ryujinx/UI/ViewModels/SettingsViewModel.cs | 5 ----- .../UI/Views/Settings/SettingsGraphicsView.axaml | 6 +++--- .../UI/Views/Settings/SettingsNetworkView.axaml | 10 ++++++++-- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/Ryujinx/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json index efc4525c09..3a3cb3017d 100644 --- a/src/Ryujinx/Assets/Locales/en_US.json +++ b/src/Ryujinx/Assets/Locales/en_US.json @@ -648,6 +648,9 @@ "GraphicsAALabel": "Anti-Aliasing:", "GraphicsScalingFilterLabel": "Scaling Filter:", "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "Level", "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "SMAA Low", @@ -664,5 +667,7 @@ "AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser.", "SettingsTabNetworkMultiplayer": "Multiplayer", "MultiplayerMode": "Mode:", - "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.", + "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index bcaa086000..fde8f74ae4 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -237,11 +237,6 @@ namespace Ryujinx.Ava.UI.ViewModels get => new(_networkInterfaces.Keys); } - public AvaloniaList MultiplayerModes - { - get => new(Enum.GetNames()); - } - public KeyboardHotkeys KeyboardHotkeys { get => _keyboardHotkeys; diff --git a/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml index 224494786f..5cffc6848a 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml +++ b/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml @@ -165,13 +165,13 @@ ToolTip.Tip="{locale:Locale GraphicsScalingFilterTooltip}" SelectedIndex="{Binding ScalingFilter}"> - + - + - + + Width="250"> + + + + + + + From 43514771bf22b3d2f666e83f0b7c7ab515821d7f Mon Sep 17 00:00:00 2001 From: SamusAranX Date: Sat, 23 Mar 2024 21:33:27 +0100 Subject: [PATCH 16/87] New gamecard icons (#6557) * Replaced all gamecard images and added a blank variant * File optimization --- .../Resources/Icon_Blank.png | Bin 0 -> 16179 bytes src/Ryujinx.UI.Common/Resources/Icon_NCA.png | Bin 10190 -> 18432 bytes src/Ryujinx.UI.Common/Resources/Icon_NRO.png | Bin 10254 -> 18404 bytes src/Ryujinx.UI.Common/Resources/Icon_NSO.png | Bin 10354 -> 18705 bytes src/Ryujinx.UI.Common/Resources/Icon_NSP.png | Bin 9899 -> 18260 bytes src/Ryujinx.UI.Common/Resources/Icon_XCI.png | Bin 9809 -> 18220 bytes 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/Ryujinx.UI.Common/Resources/Icon_Blank.png diff --git a/src/Ryujinx.UI.Common/Resources/Icon_Blank.png b/src/Ryujinx.UI.Common/Resources/Icon_Blank.png new file mode 100644 index 0000000000000000000000000000000000000000..d2bba8a92f5dd5254d10460b9f6f0af468f8a1af GIT binary patch literal 16179 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 zΠ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 literal 0 HcmV?d00001 diff --git a/src/Ryujinx.UI.Common/Resources/Icon_NCA.png b/src/Ryujinx.UI.Common/Resources/Icon_NCA.png index feae77b943f2b6f4498b6f2f5fb49e5e5726b933..99def6cfd74712bffad207f9186a78f4ae141cd1 100644 GIT binary patch literal 18432 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 zΠ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 diff --git a/src/Ryujinx.UI.Common/Resources/Icon_NRO.png b/src/Ryujinx.UI.Common/Resources/Icon_NRO.png index 3a9da621834bc55b58978ffbc7b37cc50372bb1b..6cec176ad3d9ebccb5eac6e6e5562e380457b514 100644 GIT binary patch literal 18404 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 literal 10254 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

p6aHZ8We?OnIvoaWzaIaze|KQ3zsbz*|FZ!gV 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 C(O7-} diff --git a/src/Ryujinx.UI.Common/Resources/Icon_NSO.png b/src/Ryujinx.UI.Common/Resources/Icon_NSO.png index 16de84bedbefaead611e52a86a83913cde292e06..ad88459cfc9515dbf5f8ee0a909eb35d078e37e8 100644 GIT binary patch literal 18705 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

{0=ap+O zUSx<03org(s{Z)Bh1|=_KWb*1Gnytv0bNO5T%!qtY`#ncW>^t{&D=n>64GbUt-ffua=9S3hEZ$&)@Q=r0#voZ}u19bsF~>H#9JC_^335-Fx*X=V$Hojkd6VECg_She|`GQ)}lp1+1m zr9?$rZ%(Zi{3Wf#P!rNGXB#8*oL}&H{rstYTx!qFm;F8R^ZDYd>I{E0cAgB|I?3*J z(YNT8359h%zq%jmi*Gfuwr3QOU})+1eEh`O^BeTC7OuGU=CgJ9pMyV*kKRmPbIzZE zg^xk;(I4HXiZvbViQA7%UBqcj?^E!VHb94jmSC9!W`${vLA^ zJNC-Vn8zZ)qjGcCrMq>UGoBw0kq=sN^ymBS-+pp7++ug=5ZF2U%h6}jx1%D<_kFzn zY{r$F%E|pK$y-&TYAp6`zB*t3+}*P?UMVxYl4DT(G{I?!zgfhWzJOgKOWfqo?s_hJ z$h>RQ3tO{@(!42AT70(tQ?e|~83k$>E=+0{SLA=+v}4DP<1X?iozIBoioO+` z+W9STO6a9S(>uR)Z0oxGyyJ4cN|leyPo~VD$}XaFe)}*S_|GiU`D)v~?MW643=FCz zt`Q|Ei6yC4$wjG&C8-QX21bUu1}3_O#vz8LR)$7a#wOYZ237_J$(nqeC>nC}Q!>*k XacdAN*|wE|fq}u()z4*}Q$iB}IQ%+~ diff --git a/src/Ryujinx.UI.Common/Resources/Icon_NSP.png b/src/Ryujinx.UI.Common/Resources/Icon_NSP.png index 4f98e22eafda5553d04615dba312b927693c278a..a0cef62f4c98e434d33f0554a54ad812025d959d 100644 GIT binary patch literal 18260 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 bu898;QLDK%eV-l!0|SGntDnm{r-UW|2PF|g literal 9899 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 F0RXp)Wjz1@ diff --git a/src/Ryujinx.UI.Common/Resources/Icon_XCI.png b/src/Ryujinx.UI.Common/Resources/Icon_XCI.png index f9c34f47f13e6db43afcef5a520888a2caa51044..ef33f281620a5998b261a385c1be245c6febc391 100644 GIT binary patch literal 18220 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 qHqkaPure@6*5u Date: Tue, 26 Mar 2024 22:27:48 -0300 Subject: [PATCH 17/87] Disable push descriptors for Intel ARC GPUs on Windows (#6551) * Move some init logic out of PrintGpuInformation, then delete it * Disable push descriptors for Intel ARC on Windows * Re-add PrintGpuInformation just to show it in the log --- .../ShaderCollection.cs | 8 +- src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs | 94 ++++++++++--------- 2 files changed, 58 insertions(+), 44 deletions(-) diff --git a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs index 7f687fb4cf..e4ea0e4e61 100644 --- a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs +++ b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs @@ -111,8 +111,8 @@ namespace Ryujinx.Graphics.Vulkan bool usePushDescriptors = !isMinimal && VulkanConfiguration.UsePushDescriptors && _gd.Capabilities.SupportsPushDescriptors && - !_gd.IsNvidiaPreTuring && !IsCompute && + !HasPushDescriptorsBug(gd) && CanUsePushDescriptors(gd, resourceLayout, IsCompute); ReadOnlyCollection sets = usePushDescriptors ? @@ -147,6 +147,12 @@ namespace Ryujinx.Graphics.Vulkan _firstBackgroundUse = !fromCache; } + private static bool HasPushDescriptorsBug(VulkanRenderer gd) + { + // Those GPUs/drivers do not work properly with push descriptors, so we must force disable them. + return gd.IsNvidiaPreTuring || (gd.IsIntelArc && gd.IsIntelWindows); + } + private static bool CanUsePushDescriptors(VulkanRenderer gd, ResourceLayout layout, bool isCompute) { // If binding 3 is immediately used, use an alternate set of reserved bindings. diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index 7d7c109525..ede54a6fc2 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -87,6 +87,7 @@ namespace Ryujinx.Graphics.Vulkan internal bool IsIntelWindows { get; private set; } internal bool IsAmdGcn { get; private set; } internal bool IsNvidiaPreTuring { get; private set; } + internal bool IsIntelArc { get; private set; } internal bool IsMoltenVk { get; private set; } internal bool IsTBDR { get; private set; } internal bool IsSharedMemory { get; private set; } @@ -310,6 +311,51 @@ namespace Ryujinx.Graphics.Vulkan ref var properties = ref properties2.Properties; + var hasDriverProperties = _physicalDevice.TryGetPhysicalDeviceDriverPropertiesKHR(Api, out var driverProperties); + + string vendorName = VendorUtils.GetNameFromId(properties.VendorID); + + Vendor = VendorUtils.FromId(properties.VendorID); + + IsAmdWindows = Vendor == Vendor.Amd && OperatingSystem.IsWindows(); + IsIntelWindows = Vendor == Vendor.Intel && OperatingSystem.IsWindows(); + IsTBDR = + Vendor == Vendor.Apple || + Vendor == Vendor.Qualcomm || + Vendor == Vendor.ARM || + Vendor == Vendor.Broadcom || + Vendor == Vendor.ImgTec; + + GpuVendor = vendorName; + GpuDriver = hasDriverProperties ? Marshal.PtrToStringAnsi((IntPtr)driverProperties.DriverName) : vendorName; // Fall back to vendor name if driver name isn't available. + + fixed (byte* deviceName = properties.DeviceName) + { + GpuRenderer = Marshal.PtrToStringAnsi((IntPtr)deviceName); + } + + GpuVersion = $"Vulkan v{ParseStandardVulkanVersion(properties.ApiVersion)}, Driver v{ParseDriverVersion(ref properties)}"; + + IsAmdGcn = !IsMoltenVk && Vendor == Vendor.Amd && VendorUtils.AmdGcnRegex().IsMatch(GpuRenderer); + + if (Vendor == Vendor.Nvidia) + { + var match = VendorUtils.NvidiaConsumerClassRegex().Match(GpuRenderer); + + if (match != null && int.TryParse(match.Groups[2].Value, out int gpuNumber)) + { + IsNvidiaPreTuring = gpuNumber < 2000; + } + else if (GpuDriver.Contains("TITAN") && !GpuDriver.Contains("RTX")) + { + IsNvidiaPreTuring = true; + } + } + else if (Vendor == Vendor.Intel) + { + IsIntelArc = GpuRenderer.StartsWith("Intel(R) Arc(TM)"); + } + ulong minResourceAlignment = Math.Max( Math.Max( properties.Limits.MinStorageBufferOffsetAlignment, @@ -732,49 +778,6 @@ namespace Ryujinx.Graphics.Vulkan return ParseStandardVulkanVersion(driverVersionRaw); } - private unsafe void PrintGpuInformation() - { - var properties = _physicalDevice.PhysicalDeviceProperties; - - var hasDriverProperties = _physicalDevice.TryGetPhysicalDeviceDriverPropertiesKHR(Api, out var driverProperties); - - string vendorName = VendorUtils.GetNameFromId(properties.VendorID); - - Vendor = VendorUtils.FromId(properties.VendorID); - - IsAmdWindows = Vendor == Vendor.Amd && OperatingSystem.IsWindows(); - IsIntelWindows = Vendor == Vendor.Intel && OperatingSystem.IsWindows(); - IsTBDR = - Vendor == Vendor.Apple || - Vendor == Vendor.Qualcomm || - Vendor == Vendor.ARM || - Vendor == Vendor.Broadcom || - Vendor == Vendor.ImgTec; - - GpuVendor = vendorName; - GpuDriver = hasDriverProperties ? Marshal.PtrToStringAnsi((IntPtr)driverProperties.DriverName) : vendorName; // Fall back to vendor name if driver name isn't available. - GpuRenderer = Marshal.PtrToStringAnsi((IntPtr)properties.DeviceName); - GpuVersion = $"Vulkan v{ParseStandardVulkanVersion(properties.ApiVersion)}, Driver v{ParseDriverVersion(ref properties)}"; - - IsAmdGcn = !IsMoltenVk && Vendor == Vendor.Amd && VendorUtils.AmdGcnRegex().IsMatch(GpuRenderer); - - if (Vendor == Vendor.Nvidia) - { - var match = VendorUtils.NvidiaConsumerClassRegex().Match(GpuRenderer); - - if (match != null && int.TryParse(match.Groups[2].Value, out int gpuNumber)) - { - IsNvidiaPreTuring = gpuNumber < 2000; - } - else if (GpuDriver.Contains("TITAN") && !GpuDriver.Contains("RTX")) - { - IsNvidiaPreTuring = true; - } - } - - Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})"); - } - internal PrimitiveTopology TopologyRemap(PrimitiveTopology topology) { return topology switch @@ -798,6 +801,11 @@ namespace Ryujinx.Graphics.Vulkan }; } + private void PrintGpuInformation() + { + Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})"); + } + public void Initialize(GraphicsDebugLevel logLevel) { SetupContext(logLevel); From f6d24449b6e1ebe753c0a8095a435820c0948f19 Mon Sep 17 00:00:00 2001 From: jcm Date: Tue, 26 Mar 2024 20:54:13 -0500 Subject: [PATCH 18/87] Recreate swapchain correctly when toggling VSync (#6521) Co-authored-by: jcm --- src/Ryujinx/AppHost.cs | 9 +++++++-- src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml.cs | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs index 2620ea68c6..868d194fb3 100644 --- a/src/Ryujinx/AppHost.cs +++ b/src/Ryujinx/AppHost.cs @@ -420,6 +420,12 @@ namespace Ryujinx.Ava Device.Configuration.MultiplayerMode = e.NewValue; } + public void ToggleVSync() + { + Device.EnableDeviceVsync = !Device.EnableDeviceVsync; + _renderer.Window.ChangeVSyncMode(Device.EnableDeviceVsync); + } + public void Stop() { _isActive = false; @@ -1068,8 +1074,7 @@ namespace Ryujinx.Ava switch (currentHotkeyState) { case KeyboardHotkeyState.ToggleVSync: - Device.EnableDeviceVsync = !Device.EnableDeviceVsync; - + ToggleVSync(); break; case KeyboardHotkeyState.Screenshot: ScreenshotRequested = true; diff --git a/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml.cs b/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml.cs index 239a7cbfca..5284957130 100644 --- a/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml.cs +++ b/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml.cs @@ -33,7 +33,7 @@ namespace Ryujinx.Ava.UI.Views.Main private void VsyncStatus_PointerReleased(object sender, PointerReleasedEventArgs e) { - Window.ViewModel.AppHost.Device.EnableDeviceVsync = !Window.ViewModel.AppHost.Device.EnableDeviceVsync; + Window.ViewModel.AppHost.ToggleVSync(); Logger.Info?.Print(LogClass.Application, $"VSync toggled to: {Window.ViewModel.AppHost.Device.EnableDeviceVsync}"); } From b323a017385ac6e08db4025fe4ef1bfbb41607ab Mon Sep 17 00:00:00 2001 From: gdkchan Date: Tue, 26 Mar 2024 23:33:24 -0300 Subject: [PATCH 19/87] Implement host tracked memory manager mode (#6356) * Add host tracked memory manager mode * Skipping flush is no longer needed * Formatting + revert unrelated change * LightningJit: Ensure that dest register is saved for load ops that do partial updates * avoid allocations when doing address space lookup Add missing improvement * IsRmwMemory -> IsPartialRegisterUpdateMemory * Ensure we iterate all private allocations in range * PR feedback and potential fixes * Simplified bridges a lot * Skip calling SignalMappingChanged if Guest is true * Late map bridge too * Force address masking for prefetch instructions * Reprotection for bridges * Move partition list validation to separate debug method * Move host tracked related classes to HostTracked folder * New HostTracked namespace * Move host tracked modes to the end of enum to avoid PPTC invalidation --------- Co-authored-by: riperiperi --- .../Instructions/InstEmitMemoryHelper.cs | 31 +- src/ARMeilleure/Memory/MemoryManagerType.cs | 22 + .../Signal/NativeSignalHandlerGenerator.cs | 101 ++- .../Collections/IntrusiveRedBlackTreeNode.cs | 8 +- .../AppleHv/HvMemoryBlockAllocator.cs | 2 +- .../AddressIntrusiveRedBlackTree.cs | 35 + .../Jit/HostTracked/AddressSpacePartition.cs | 708 ++++++++++++++++++ .../AddressSpacePartitionAllocator.cs | 202 +++++ .../AddressSpacePartitionMultiAllocation.cs | 101 +++ .../HostTracked/AddressSpacePartitioned.cs | 407 ++++++++++ .../Jit/HostTracked/NativePageTable.cs | 223 ++++++ src/Ryujinx.Cpu/Jit/JitCpuContext.cs | 4 +- .../Jit/MemoryManagerHostTracked.cs | 627 ++++++++++++++++ .../Arm32/Target/Arm64/InstEmitMemory.cs | 20 +- .../LightningJit/Arm64/InstName.cs | 32 + .../LightningJit/Arm64/RegisterAllocator.cs | 19 +- .../LightningJit/Arm64/RegisterUtils.cs | 4 +- .../Arm64/Target/Arm64/Compiler.cs | 2 +- .../Arm64/Target/Arm64/Decoder.cs | 7 +- .../Arm64/Target/Arm64/InstEmitMemory.cs | 46 +- src/Ryujinx.Cpu/LightningJit/Translator.cs | 4 +- src/Ryujinx.Cpu/MemoryEhMeilleure.cs | 32 +- src/Ryujinx.Cpu/PrivateMemoryAllocator.cs | 8 +- src/Ryujinx.Cpu/Signal/NativeSignalHandler.cs | 11 +- .../Image/TextureGroup.cs | 8 - .../Memory/PhysicalMemory.cs | 5 - .../HOS/ArmProcessContextFactory.cs | 19 +- .../HOS/Kernel/Memory/KPageTable.cs | 25 + src/Ryujinx.Memory/AddressSpaceManager.cs | 4 +- src/Ryujinx.Memory/Tracking/VirtualRegion.cs | 7 +- 30 files changed, 2648 insertions(+), 76 deletions(-) create mode 100644 src/Ryujinx.Cpu/Jit/HostTracked/AddressIntrusiveRedBlackTree.cs create mode 100644 src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartition.cs create mode 100644 src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitionAllocator.cs create mode 100644 src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitionMultiAllocation.cs create mode 100644 src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitioned.cs create mode 100644 src/Ryujinx.Cpu/Jit/HostTracked/NativePageTable.cs create mode 100644 src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs diff --git a/src/ARMeilleure/Instructions/InstEmitMemoryHelper.cs b/src/ARMeilleure/Instructions/InstEmitMemoryHelper.cs index a807eed51c..ace6fe1ce9 100644 --- a/src/ARMeilleure/Instructions/InstEmitMemoryHelper.cs +++ b/src/ARMeilleure/Instructions/InstEmitMemoryHelper.cs @@ -157,7 +157,7 @@ namespace ARMeilleure.Instructions context.Copy(temp, value); - if (!context.Memory.Type.IsHostMapped()) + if (!context.Memory.Type.IsHostMappedOrTracked()) { context.Branch(lblEnd); @@ -198,7 +198,7 @@ namespace ARMeilleure.Instructions SetInt(context, rt, value); - if (!context.Memory.Type.IsHostMapped()) + if (!context.Memory.Type.IsHostMappedOrTracked()) { context.Branch(lblEnd); @@ -265,7 +265,7 @@ namespace ARMeilleure.Instructions context.Copy(GetVec(rt), value); - if (!context.Memory.Type.IsHostMapped()) + if (!context.Memory.Type.IsHostMappedOrTracked()) { context.Branch(lblEnd); @@ -312,7 +312,7 @@ namespace ARMeilleure.Instructions break; } - if (!context.Memory.Type.IsHostMapped()) + if (!context.Memory.Type.IsHostMappedOrTracked()) { context.Branch(lblEnd); @@ -385,7 +385,7 @@ namespace ARMeilleure.Instructions break; } - if (!context.Memory.Type.IsHostMapped()) + if (!context.Memory.Type.IsHostMappedOrTracked()) { context.Branch(lblEnd); @@ -403,6 +403,27 @@ namespace ARMeilleure.Instructions { return EmitHostMappedPointer(context, address); } + else if (context.Memory.Type.IsHostTracked()) + { + if (address.Type == OperandType.I32) + { + address = context.ZeroExtend32(OperandType.I64, address); + } + + if (context.Memory.Type == MemoryManagerType.HostTracked) + { + Operand mask = Const(ulong.MaxValue >> (64 - context.Memory.AddressSpaceBits)); + address = context.BitwiseAnd(address, mask); + } + + Operand ptBase = !context.HasPtc + ? Const(context.Memory.PageTablePointer.ToInt64()) + : Const(context.Memory.PageTablePointer.ToInt64(), Ptc.PageTableSymbol); + + Operand ptOffset = context.ShiftRightUI(address, Const(PageBits)); + + return context.Add(address, context.Load(OperandType.I64, context.Add(ptBase, context.ShiftLeft(ptOffset, Const(3))))); + } int ptLevelBits = context.Memory.AddressSpaceBits - PageBits; int ptLevelSize = 1 << ptLevelBits; diff --git a/src/ARMeilleure/Memory/MemoryManagerType.cs b/src/ARMeilleure/Memory/MemoryManagerType.cs index b1cdbb069a..bc8ae26359 100644 --- a/src/ARMeilleure/Memory/MemoryManagerType.cs +++ b/src/ARMeilleure/Memory/MemoryManagerType.cs @@ -29,6 +29,18 @@ namespace ARMeilleure.Memory /// Allows invalid access from JIT code to the rest of the program, but is faster. ///

HostMappedUnsafe, + + /// + /// High level implementation using a software flat page table for address translation + /// with no support for handling invalid or non-contiguous memory access. + /// + HostTracked, + + /// + /// High level implementation using a software flat page table for address translation + /// without masking the address and no support for handling invalid or non-contiguous memory access. + /// + HostTrackedUnsafe, } public static class MemoryManagerTypeExtensions @@ -37,5 +49,15 @@ namespace ARMeilleure.Memory { return type == MemoryManagerType.HostMapped || type == MemoryManagerType.HostMappedUnsafe; } + + public static bool IsHostTracked(this MemoryManagerType type) + { + return type == MemoryManagerType.HostTracked || type == MemoryManagerType.HostTrackedUnsafe; + } + + public static bool IsHostMappedOrTracked(this MemoryManagerType type) + { + return type.IsHostMapped() || type.IsHostTracked(); + } } } diff --git a/src/ARMeilleure/Signal/NativeSignalHandlerGenerator.cs b/src/ARMeilleure/Signal/NativeSignalHandlerGenerator.cs index c5e708e169..2ec5bc1b38 100644 --- a/src/ARMeilleure/Signal/NativeSignalHandlerGenerator.cs +++ b/src/ARMeilleure/Signal/NativeSignalHandlerGenerator.cs @@ -21,10 +21,8 @@ namespace ARMeilleure.Signal private const uint EXCEPTION_ACCESS_VIOLATION = 0xc0000005; - private static Operand EmitGenericRegionCheck(EmitterContext context, IntPtr signalStructPtr, Operand faultAddress, Operand isWrite, int rangeStructSize, ulong pageSize) + private static Operand EmitGenericRegionCheck(EmitterContext context, IntPtr signalStructPtr, Operand faultAddress, Operand isWrite, int rangeStructSize) { - ulong pageMask = pageSize - 1; - Operand inRegionLocal = context.AllocateLocal(OperandType.I32); context.Copy(inRegionLocal, Const(0)); @@ -51,7 +49,7 @@ namespace ARMeilleure.Signal // Only call tracking if in range. context.BranchIfFalse(nextLabel, inRange, BasicBlockFrequency.Cold); - Operand offset = context.BitwiseAnd(context.Subtract(faultAddress, rangeAddress), Const(~pageMask)); + Operand offset = context.Subtract(faultAddress, rangeAddress); // Call the tracking action, with the pointer's relative offset to the base address. Operand trackingActionPtr = context.Load(OperandType.I64, Const((ulong)signalStructPtr + rangeBaseOffset + 20)); @@ -62,8 +60,10 @@ namespace ARMeilleure.Signal // Tracking action should be non-null to call it, otherwise assume false return. context.BranchIfFalse(skipActionLabel, trackingActionPtr); - Operand result = context.Call(trackingActionPtr, OperandType.I32, offset, Const(pageSize), isWrite); - context.Copy(inRegionLocal, result); + Operand result = context.Call(trackingActionPtr, OperandType.I64, offset, Const(1UL), isWrite); + context.Copy(inRegionLocal, context.ICompareNotEqual(result, Const(0UL))); + + GenerateFaultAddressPatchCode(context, faultAddress, result); context.MarkLabel(skipActionLabel); @@ -155,7 +155,7 @@ namespace ARMeilleure.Signal throw new PlatformNotSupportedException(); } - public static byte[] GenerateUnixSignalHandler(IntPtr signalStructPtr, int rangeStructSize, ulong pageSize) + public static byte[] GenerateUnixSignalHandler(IntPtr signalStructPtr, int rangeStructSize) { EmitterContext context = new(); @@ -168,7 +168,7 @@ namespace ARMeilleure.Signal Operand isWrite = context.ICompareNotEqual(writeFlag, Const(0L)); // Normalize to 0/1. - Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite, rangeStructSize, pageSize); + Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite, rangeStructSize); Operand endLabel = Label(); @@ -203,7 +203,7 @@ namespace ARMeilleure.Signal return Compiler.Compile(cfg, argTypes, OperandType.None, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Code; } - public static byte[] GenerateWindowsSignalHandler(IntPtr signalStructPtr, int rangeStructSize, ulong pageSize) + public static byte[] GenerateWindowsSignalHandler(IntPtr signalStructPtr, int rangeStructSize) { EmitterContext context = new(); @@ -232,7 +232,7 @@ namespace ARMeilleure.Signal Operand isWrite = context.ICompareNotEqual(writeFlag, Const(0L)); // Normalize to 0/1. - Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite, rangeStructSize, pageSize); + Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite, rangeStructSize); Operand endLabel = Label(); @@ -256,5 +256,86 @@ namespace ARMeilleure.Signal return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Code; } + + private static void GenerateFaultAddressPatchCode(EmitterContext context, Operand faultAddress, Operand newAddress) + { + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + if (SupportsFaultAddressPatchingForHostOs()) + { + Operand lblSkip = Label(); + + context.BranchIf(lblSkip, faultAddress, newAddress, Comparison.Equal); + + Operand ucontextPtr = context.LoadArgument(OperandType.I64, 2); + Operand pcCtxAddress = default; + ulong baseRegsOffset = 0; + + if (OperatingSystem.IsLinux()) + { + pcCtxAddress = context.Add(ucontextPtr, Const(440UL)); + baseRegsOffset = 184UL; + } + else if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS()) + { + ucontextPtr = context.Load(OperandType.I64, context.Add(ucontextPtr, Const(48UL))); + + pcCtxAddress = context.Add(ucontextPtr, Const(272UL)); + baseRegsOffset = 16UL; + } + + Operand pc = context.Load(OperandType.I64, pcCtxAddress); + + Operand reg = GetAddressRegisterFromArm64Instruction(context, pc); + Operand reg64 = context.ZeroExtend32(OperandType.I64, reg); + Operand regCtxAddress = context.Add(ucontextPtr, context.Add(context.ShiftLeft(reg64, Const(3)), Const(baseRegsOffset))); + Operand regAddress = context.Load(OperandType.I64, regCtxAddress); + + Operand addressDelta = context.Subtract(regAddress, faultAddress); + + context.Store(regCtxAddress, context.Add(newAddress, addressDelta)); + + context.MarkLabel(lblSkip); + } + } + } + + private static Operand GetAddressRegisterFromArm64Instruction(EmitterContext context, Operand pc) + { + Operand inst = context.Load(OperandType.I32, pc); + Operand reg = context.AllocateLocal(OperandType.I32); + + Operand isSysInst = context.ICompareEqual(context.BitwiseAnd(inst, Const(0xFFF80000)), Const(0xD5080000)); + + Operand lblSys = Label(); + Operand lblEnd = Label(); + + context.BranchIfTrue(lblSys, isSysInst, BasicBlockFrequency.Cold); + + context.Copy(reg, context.BitwiseAnd(context.ShiftRightUI(inst, Const(5)), Const(0x1F))); + context.Branch(lblEnd); + + context.MarkLabel(lblSys); + context.Copy(reg, context.BitwiseAnd(inst, Const(0x1F))); + + context.MarkLabel(lblEnd); + + return reg; + } + + public static bool SupportsFaultAddressPatchingForHost() + { + return SupportsFaultAddressPatchingForHostArch() && SupportsFaultAddressPatchingForHostOs(); + } + + private static bool SupportsFaultAddressPatchingForHostArch() + { + return RuntimeInformation.ProcessArchitecture == Architecture.Arm64; + } + + private static bool SupportsFaultAddressPatchingForHostOs() + { + return OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS(); + } } } diff --git a/src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeNode.cs b/src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeNode.cs index 8480d51ad6..29d2d0c9a8 100644 --- a/src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeNode.cs +++ b/src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeNode.cs @@ -5,10 +5,10 @@ namespace Ryujinx.Common.Collections ///
public class IntrusiveRedBlackTreeNode where T : IntrusiveRedBlackTreeNode { - internal bool Color = true; - internal T Left; - internal T Right; - internal T Parent; + public bool Color = true; + public T Left; + public T Right; + public T Parent; public T Predecessor => IntrusiveRedBlackTreeImpl.PredecessorOf((T)this); public T Successor => IntrusiveRedBlackTreeImpl.SuccessorOf((T)this); diff --git a/src/Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocator.cs b/src/Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocator.cs index 4e3723d554..86936c5929 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocator.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocator.cs @@ -38,7 +38,7 @@ namespace Ryujinx.Cpu.AppleHv private readonly HvIpaAllocator _ipaAllocator; - public HvMemoryBlockAllocator(HvIpaAllocator ipaAllocator, int blockAlignment) : base(blockAlignment, MemoryAllocationFlags.None) + public HvMemoryBlockAllocator(HvIpaAllocator ipaAllocator, ulong blockAlignment) : base(blockAlignment, MemoryAllocationFlags.None) { _ipaAllocator = ipaAllocator; } diff --git a/src/Ryujinx.Cpu/Jit/HostTracked/AddressIntrusiveRedBlackTree.cs b/src/Ryujinx.Cpu/Jit/HostTracked/AddressIntrusiveRedBlackTree.cs new file mode 100644 index 0000000000..0e24433035 --- /dev/null +++ b/src/Ryujinx.Cpu/Jit/HostTracked/AddressIntrusiveRedBlackTree.cs @@ -0,0 +1,35 @@ +using Ryujinx.Common.Collections; +using System; + +namespace Ryujinx.Cpu.Jit.HostTracked +{ + internal class AddressIntrusiveRedBlackTree : IntrusiveRedBlackTree where T : IntrusiveRedBlackTreeNode, IComparable, IComparable + { + /// + /// Retrieve the node that is considered equal to the specified address by the comparator. + /// + /// Address to compare with + /// Node that is equal to + public T GetNode(ulong address) + { + T node = Root; + while (node != null) + { + int cmp = node.CompareTo(address); + if (cmp < 0) + { + node = node.Left; + } + else if (cmp > 0) + { + node = node.Right; + } + else + { + return node; + } + } + return null; + } + } +} diff --git a/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartition.cs b/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartition.cs new file mode 100644 index 0000000000..224c5edc30 --- /dev/null +++ b/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartition.cs @@ -0,0 +1,708 @@ +using Ryujinx.Common; +using Ryujinx.Common.Collections; +using Ryujinx.Memory; +using System; +using System.Diagnostics; +using System.Threading; + +namespace Ryujinx.Cpu.Jit.HostTracked +{ + readonly struct PrivateRange + { + public readonly MemoryBlock Memory; + public readonly ulong Offset; + public readonly ulong Size; + + public static PrivateRange Empty => new(null, 0, 0); + + public PrivateRange(MemoryBlock memory, ulong offset, ulong size) + { + Memory = memory; + Offset = offset; + Size = size; + } + } + + class AddressSpacePartition : IDisposable + { + public const ulong GuestPageSize = 0x1000; + + private const int DefaultBlockAlignment = 1 << 20; + + private enum MappingType : byte + { + None, + Private, + } + + private class Mapping : IntrusiveRedBlackTreeNode, IComparable, IComparable + { + public ulong Address { get; private set; } + public ulong Size { get; private set; } + public ulong EndAddress => Address + Size; + public MappingType Type { get; private set; } + + public Mapping(ulong address, ulong size, MappingType type) + { + Address = address; + Size = size; + Type = type; + } + + public Mapping Split(ulong splitAddress) + { + ulong leftSize = splitAddress - Address; + ulong rightSize = EndAddress - splitAddress; + + Mapping left = new(Address, leftSize, Type); + + Address = splitAddress; + Size = rightSize; + + return left; + } + + public void UpdateState(MappingType newType) + { + Type = newType; + } + + public void Extend(ulong sizeDelta) + { + Size += sizeDelta; + } + + public int CompareTo(Mapping other) + { + if (Address < other.Address) + { + return -1; + } + else if (Address <= other.EndAddress - 1UL) + { + return 0; + } + else + { + return 1; + } + } + + public int CompareTo(ulong address) + { + if (address < Address) + { + return -1; + } + else if (address <= EndAddress - 1UL) + { + return 0; + } + else + { + return 1; + } + } + } + + private class PrivateMapping : IntrusiveRedBlackTreeNode, IComparable, IComparable + { + public ulong Address { get; private set; } + public ulong Size { get; private set; } + public ulong EndAddress => Address + Size; + public PrivateMemoryAllocation PrivateAllocation { get; private set; } + + public PrivateMapping(ulong address, ulong size, PrivateMemoryAllocation privateAllocation) + { + Address = address; + Size = size; + PrivateAllocation = privateAllocation; + } + + public PrivateMapping Split(ulong splitAddress) + { + ulong leftSize = splitAddress - Address; + ulong rightSize = EndAddress - splitAddress; + + Debug.Assert(leftSize > 0); + Debug.Assert(rightSize > 0); + + (var leftAllocation, PrivateAllocation) = PrivateAllocation.Split(leftSize); + + PrivateMapping left = new(Address, leftSize, leftAllocation); + + Address = splitAddress; + Size = rightSize; + + return left; + } + + public void Map(AddressSpacePartitionMultiAllocation baseBlock, ulong baseAddress, PrivateMemoryAllocation newAllocation) + { + baseBlock.MapView(newAllocation.Memory, newAllocation.Offset, Address - baseAddress, Size); + PrivateAllocation = newAllocation; + } + + public void Unmap(AddressSpacePartitionMultiAllocation baseBlock, ulong baseAddress) + { + if (PrivateAllocation.IsValid) + { + baseBlock.UnmapView(PrivateAllocation.Memory, Address - baseAddress, Size); + PrivateAllocation.Dispose(); + } + + PrivateAllocation = default; + } + + public void Extend(ulong sizeDelta) + { + Size += sizeDelta; + } + + public int CompareTo(PrivateMapping other) + { + if (Address < other.Address) + { + return -1; + } + else if (Address <= other.EndAddress - 1UL) + { + return 0; + } + else + { + return 1; + } + } + + public int CompareTo(ulong address) + { + if (address < Address) + { + return -1; + } + else if (address <= EndAddress - 1UL) + { + return 0; + } + else + { + return 1; + } + } + } + + private readonly MemoryBlock _backingMemory; + private readonly AddressSpacePartitionMultiAllocation _baseMemory; + private readonly PrivateMemoryAllocator _privateMemoryAllocator; + + private readonly AddressIntrusiveRedBlackTree _mappingTree; + private readonly AddressIntrusiveRedBlackTree _privateTree; + + private readonly ReaderWriterLockSlim _treeLock; + + private readonly ulong _hostPageSize; + + private ulong? _firstPagePa; + private ulong? _lastPagePa; + private ulong _cachedFirstPagePa; + private ulong _cachedLastPagePa; + private MemoryBlock _firstPageMemoryForUnmap; + private ulong _firstPageOffsetForLateMap; + private MemoryPermission _firstPageMemoryProtection; + + public ulong Address { get; } + public ulong Size { get; } + public ulong EndAddress => Address + Size; + + public AddressSpacePartition(AddressSpacePartitionAllocation baseMemory, MemoryBlock backingMemory, ulong address, ulong size) + { + _privateMemoryAllocator = new PrivateMemoryAllocator(DefaultBlockAlignment, MemoryAllocationFlags.Mirrorable); + _mappingTree = new AddressIntrusiveRedBlackTree(); + _privateTree = new AddressIntrusiveRedBlackTree(); + _treeLock = new ReaderWriterLockSlim(); + + _mappingTree.Add(new Mapping(address, size, MappingType.None)); + _privateTree.Add(new PrivateMapping(address, size, default)); + + _hostPageSize = MemoryBlock.GetPageSize(); + + _backingMemory = backingMemory; + _baseMemory = new(baseMemory); + + _cachedFirstPagePa = ulong.MaxValue; + _cachedLastPagePa = ulong.MaxValue; + + Address = address; + Size = size; + } + + public bool IsEmpty() + { + _treeLock.EnterReadLock(); + + try + { + Mapping map = _mappingTree.GetNode(Address); + + return map != null && map.Address == Address && map.Size == Size && map.Type == MappingType.None; + } + finally + { + _treeLock.ExitReadLock(); + } + } + + public void Map(ulong va, ulong pa, ulong size) + { + Debug.Assert(va >= Address); + Debug.Assert(va + size <= EndAddress); + + if (va == Address) + { + _firstPagePa = pa; + } + + if (va <= EndAddress - GuestPageSize && va + size > EndAddress - GuestPageSize) + { + _lastPagePa = pa + ((EndAddress - GuestPageSize) - va); + } + + Update(va, pa, size, MappingType.Private); + } + + public void Unmap(ulong va, ulong size) + { + Debug.Assert(va >= Address); + Debug.Assert(va + size <= EndAddress); + + if (va == Address) + { + _firstPagePa = null; + } + + if (va <= EndAddress - GuestPageSize && va + size > EndAddress - GuestPageSize) + { + _lastPagePa = null; + } + + Update(va, 0UL, size, MappingType.None); + } + + public void ReprotectAligned(ulong va, ulong size, MemoryPermission protection) + { + Debug.Assert(va >= Address); + Debug.Assert(va + size <= EndAddress); + + _baseMemory.Reprotect(va - Address, size, protection, false); + + if (va == Address) + { + _firstPageMemoryProtection = protection; + } + } + + public void Reprotect( + ulong va, + ulong size, + MemoryPermission protection, + AddressSpacePartitioned addressSpace, + Action updatePtCallback) + { + if (_baseMemory.LazyInitMirrorForProtection(addressSpace, Address, Size, protection)) + { + LateMap(); + } + + updatePtCallback(va, _baseMemory.GetPointerForProtection(va - Address, size, protection), size); + } + + public IntPtr GetPointer(ulong va, ulong size) + { + Debug.Assert(va >= Address); + Debug.Assert(va + size <= EndAddress); + + return _baseMemory.GetPointer(va - Address, size); + } + + public void InsertBridgeAtEnd(AddressSpacePartition partitionAfter, bool useProtectionMirrors) + { + ulong firstPagePa = partitionAfter?._firstPagePa ?? ulong.MaxValue; + ulong lastPagePa = _lastPagePa ?? ulong.MaxValue; + + if (firstPagePa != _cachedFirstPagePa || lastPagePa != _cachedLastPagePa) + { + if (partitionAfter != null && partitionAfter._firstPagePa.HasValue) + { + (MemoryBlock firstPageMemory, ulong firstPageOffset) = partitionAfter.GetFirstPageMemoryAndOffset(); + + _baseMemory.MapView(firstPageMemory, firstPageOffset, Size, _hostPageSize); + + if (!useProtectionMirrors) + { + _baseMemory.Reprotect(Size, _hostPageSize, partitionAfter._firstPageMemoryProtection, throwOnFail: false); + } + + _firstPageMemoryForUnmap = firstPageMemory; + _firstPageOffsetForLateMap = firstPageOffset; + } + else + { + MemoryBlock firstPageMemoryForUnmap = _firstPageMemoryForUnmap; + + if (firstPageMemoryForUnmap != null) + { + _baseMemory.UnmapView(firstPageMemoryForUnmap, Size, _hostPageSize); + _firstPageMemoryForUnmap = null; + } + } + + _cachedFirstPagePa = firstPagePa; + _cachedLastPagePa = lastPagePa; + } + } + + public void ReprotectBridge(MemoryPermission protection) + { + if (_firstPageMemoryForUnmap != null) + { + _baseMemory.Reprotect(Size, _hostPageSize, protection, throwOnFail: false); + } + } + + private (MemoryBlock, ulong) GetFirstPageMemoryAndOffset() + { + _treeLock.EnterReadLock(); + + try + { + PrivateMapping map = _privateTree.GetNode(Address); + + if (map != null && map.PrivateAllocation.IsValid) + { + return (map.PrivateAllocation.Memory, map.PrivateAllocation.Offset + (Address - map.Address)); + } + } + finally + { + _treeLock.ExitReadLock(); + } + + return (_backingMemory, _firstPagePa.Value); + } + + public PrivateRange GetPrivateAllocation(ulong va) + { + _treeLock.EnterReadLock(); + + try + { + PrivateMapping map = _privateTree.GetNode(va); + + if (map != null && map.PrivateAllocation.IsValid) + { + return new(map.PrivateAllocation.Memory, map.PrivateAllocation.Offset + (va - map.Address), map.Size - (va - map.Address)); + } + } + finally + { + _treeLock.ExitReadLock(); + } + + return PrivateRange.Empty; + } + + private void Update(ulong va, ulong pa, ulong size, MappingType type) + { + _treeLock.EnterWriteLock(); + + try + { + Mapping map = _mappingTree.GetNode(va); + + Update(map, va, pa, size, type); + } + finally + { + _treeLock.ExitWriteLock(); + } + } + + private Mapping Update(Mapping map, ulong va, ulong pa, ulong size, MappingType type) + { + ulong endAddress = va + size; + + for (; map != null; map = map.Successor) + { + if (map.Address < va) + { + _mappingTree.Add(map.Split(va)); + } + + if (map.EndAddress > endAddress) + { + Mapping newMap = map.Split(endAddress); + _mappingTree.Add(newMap); + map = newMap; + } + + switch (type) + { + case MappingType.None: + ulong alignment = _hostPageSize; + + bool unmappedBefore = map.Predecessor == null || + (map.Predecessor.Type == MappingType.None && map.Predecessor.Address <= BitUtils.AlignDown(va, alignment)); + + bool unmappedAfter = map.Successor == null || + (map.Successor.Type == MappingType.None && map.Successor.EndAddress >= BitUtils.AlignUp(endAddress, alignment)); + + UnmapPrivate(va, size, unmappedBefore, unmappedAfter); + break; + case MappingType.Private: + MapPrivate(va, size); + break; + } + + map.UpdateState(type); + map = TryCoalesce(map); + + if (map.EndAddress >= endAddress) + { + break; + } + } + + return map; + } + + private Mapping TryCoalesce(Mapping map) + { + Mapping previousMap = map.Predecessor; + Mapping nextMap = map.Successor; + + if (previousMap != null && CanCoalesce(previousMap, map)) + { + previousMap.Extend(map.Size); + _mappingTree.Remove(map); + map = previousMap; + } + + if (nextMap != null && CanCoalesce(map, nextMap)) + { + map.Extend(nextMap.Size); + _mappingTree.Remove(nextMap); + } + + return map; + } + + private static bool CanCoalesce(Mapping left, Mapping right) + { + return left.Type == right.Type; + } + + private void MapPrivate(ulong va, ulong size) + { + ulong endAddress = va + size; + + ulong alignment = _hostPageSize; + + // Expand the range outwards based on page size to ensure that at least the requested region is mapped. + ulong vaAligned = BitUtils.AlignDown(va, alignment); + ulong endAddressAligned = BitUtils.AlignUp(endAddress, alignment); + + PrivateMapping map = _privateTree.GetNode(va); + + for (; map != null; map = map.Successor) + { + if (!map.PrivateAllocation.IsValid) + { + if (map.Address < vaAligned) + { + _privateTree.Add(map.Split(vaAligned)); + } + + if (map.EndAddress > endAddressAligned) + { + PrivateMapping newMap = map.Split(endAddressAligned); + _privateTree.Add(newMap); + map = newMap; + } + + map.Map(_baseMemory, Address, _privateMemoryAllocator.Allocate(map.Size, _hostPageSize)); + } + + if (map.EndAddress >= endAddressAligned) + { + break; + } + } + } + + private void UnmapPrivate(ulong va, ulong size, bool unmappedBefore, bool unmappedAfter) + { + ulong endAddress = va + size; + + ulong alignment = _hostPageSize; + + // If the adjacent mappings are unmapped, expand the range outwards, + // otherwise shrink it inwards. We must ensure we won't unmap pages that might still be in use. + ulong vaAligned = unmappedBefore ? BitUtils.AlignDown(va, alignment) : BitUtils.AlignUp(va, alignment); + ulong endAddressAligned = unmappedAfter ? BitUtils.AlignUp(endAddress, alignment) : BitUtils.AlignDown(endAddress, alignment); + + if (endAddressAligned <= vaAligned) + { + return; + } + + PrivateMapping map = _privateTree.GetNode(vaAligned); + + for (; map != null; map = map.Successor) + { + if (map.PrivateAllocation.IsValid) + { + if (map.Address < vaAligned) + { + _privateTree.Add(map.Split(vaAligned)); + } + + if (map.EndAddress > endAddressAligned) + { + PrivateMapping newMap = map.Split(endAddressAligned); + _privateTree.Add(newMap); + map = newMap; + } + + map.Unmap(_baseMemory, Address); + map = TryCoalesce(map); + } + + if (map.EndAddress >= endAddressAligned) + { + break; + } + } + } + + private PrivateMapping TryCoalesce(PrivateMapping map) + { + PrivateMapping previousMap = map.Predecessor; + PrivateMapping nextMap = map.Successor; + + if (previousMap != null && CanCoalesce(previousMap, map)) + { + previousMap.Extend(map.Size); + _privateTree.Remove(map); + map = previousMap; + } + + if (nextMap != null && CanCoalesce(map, nextMap)) + { + map.Extend(nextMap.Size); + _privateTree.Remove(nextMap); + } + + return map; + } + + private static bool CanCoalesce(PrivateMapping left, PrivateMapping right) + { + return !left.PrivateAllocation.IsValid && !right.PrivateAllocation.IsValid; + } + + private void LateMap() + { + // Map all existing private allocations. + // This is necessary to ensure mirrors that are lazily created have the same mappings as the main one. + + PrivateMapping map = _privateTree.GetNode(Address); + + for (; map != null; map = map.Successor) + { + if (map.PrivateAllocation.IsValid) + { + _baseMemory.LateMapView(map.PrivateAllocation.Memory, map.PrivateAllocation.Offset, map.Address - Address, map.Size); + } + } + + MemoryBlock firstPageMemory = _firstPageMemoryForUnmap; + ulong firstPageOffset = _firstPageOffsetForLateMap; + + if (firstPageMemory != null) + { + _baseMemory.LateMapView(firstPageMemory, firstPageOffset, Size, _hostPageSize); + } + } + + public PrivateRange GetFirstPrivateAllocation(ulong va, ulong size, out ulong nextVa) + { + _treeLock.EnterReadLock(); + + try + { + PrivateMapping map = _privateTree.GetNode(va); + + nextVa = map.EndAddress; + + if (map != null && map.PrivateAllocation.IsValid) + { + ulong startOffset = va - map.Address; + + return new( + map.PrivateAllocation.Memory, + map.PrivateAllocation.Offset + startOffset, + Math.Min(map.PrivateAllocation.Size - startOffset, size)); + } + } + finally + { + _treeLock.ExitReadLock(); + } + + return PrivateRange.Empty; + } + + public bool HasPrivateAllocation(ulong va, ulong size, ulong startVa, ulong startSize, ref PrivateRange range) + { + ulong endVa = va + size; + + _treeLock.EnterReadLock(); + + try + { + for (PrivateMapping map = _privateTree.GetNode(va); map != null && map.Address < endVa; map = map.Successor) + { + if (map.PrivateAllocation.IsValid) + { + if (map.Address <= startVa && map.EndAddress >= startVa + startSize) + { + ulong startOffset = startVa - map.Address; + + range = new( + map.PrivateAllocation.Memory, + map.PrivateAllocation.Offset + startOffset, + Math.Min(map.PrivateAllocation.Size - startOffset, startSize)); + } + + return true; + } + } + } + finally + { + _treeLock.ExitReadLock(); + } + + return false; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + + _privateMemoryAllocator.Dispose(); + _baseMemory.Dispose(); + } + } +} diff --git a/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitionAllocator.cs b/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitionAllocator.cs new file mode 100644 index 0000000000..44dedb6404 --- /dev/null +++ b/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitionAllocator.cs @@ -0,0 +1,202 @@ +using Ryujinx.Common; +using Ryujinx.Common.Collections; +using Ryujinx.Memory; +using Ryujinx.Memory.Tracking; +using System; + +namespace Ryujinx.Cpu.Jit.HostTracked +{ + readonly struct AddressSpacePartitionAllocation : IDisposable + { + private readonly AddressSpacePartitionAllocator _owner; + private readonly PrivateMemoryAllocatorImpl.Allocation _allocation; + + public IntPtr Pointer => (IntPtr)((ulong)_allocation.Block.Memory.Pointer + _allocation.Offset); + + public bool IsValid => _owner != null; + + public AddressSpacePartitionAllocation( + AddressSpacePartitionAllocator owner, + PrivateMemoryAllocatorImpl.Allocation allocation) + { + _owner = owner; + _allocation = allocation; + } + + public void RegisterMapping(ulong va, ulong endVa) + { + _allocation.Block.AddMapping(_allocation.Offset, _allocation.Size, va, endVa); + } + + public void MapView(MemoryBlock srcBlock, ulong srcOffset, ulong dstOffset, ulong size) + { + _allocation.Block.Memory.MapView(srcBlock, srcOffset, _allocation.Offset + dstOffset, size); + } + + public void UnmapView(MemoryBlock srcBlock, ulong offset, ulong size) + { + _allocation.Block.Memory.UnmapView(srcBlock, _allocation.Offset + offset, size); + } + + public void Reprotect(ulong offset, ulong size, MemoryPermission permission, bool throwOnFail) + { + _allocation.Block.Memory.Reprotect(_allocation.Offset + offset, size, permission, throwOnFail); + } + + public IntPtr GetPointer(ulong offset, ulong size) + { + return _allocation.Block.Memory.GetPointer(_allocation.Offset + offset, size); + } + + public void Dispose() + { + _allocation.Block.RemoveMapping(_allocation.Offset, _allocation.Size); + _owner.Free(_allocation.Block, _allocation.Offset, _allocation.Size); + } + } + + class AddressSpacePartitionAllocator : PrivateMemoryAllocatorImpl + { + private const ulong DefaultBlockAlignment = 1UL << 32; // 4GB + + public class Block : PrivateMemoryAllocator.Block + { + private readonly MemoryTracking _tracking; + private readonly Func _readPtCallback; + private readonly MemoryEhMeilleure _memoryEh; + + private class Mapping : IntrusiveRedBlackTreeNode, IComparable, IComparable + { + public ulong Address { get; } + public ulong Size { get; } + public ulong EndAddress => Address + Size; + public ulong Va { get; } + public ulong EndVa { get; } + + public Mapping(ulong address, ulong size, ulong va, ulong endVa) + { + Address = address; + Size = size; + Va = va; + EndVa = endVa; + } + + public int CompareTo(Mapping other) + { + if (Address < other.Address) + { + return -1; + } + else if (Address <= other.EndAddress - 1UL) + { + return 0; + } + else + { + return 1; + } + } + + public int CompareTo(ulong address) + { + if (address < Address) + { + return -1; + } + else if (address <= EndAddress - 1UL) + { + return 0; + } + else + { + return 1; + } + } + } + + private readonly AddressIntrusiveRedBlackTree _mappingTree; + private readonly object _lock; + + public Block(MemoryTracking tracking, Func readPtCallback, MemoryBlock memory, ulong size, object locker) : base(memory, size) + { + _tracking = tracking; + _readPtCallback = readPtCallback; + _memoryEh = new(memory, null, tracking, VirtualMemoryEvent); + _mappingTree = new(); + _lock = locker; + } + + public void AddMapping(ulong offset, ulong size, ulong va, ulong endVa) + { + _mappingTree.Add(new(offset, size, va, endVa)); + } + + public void RemoveMapping(ulong offset, ulong size) + { + _mappingTree.Remove(_mappingTree.GetNode(offset)); + } + + private ulong VirtualMemoryEvent(ulong address, ulong size, bool write) + { + Mapping map; + + lock (_lock) + { + map = _mappingTree.GetNode(address); + } + + if (map == null) + { + return 0; + } + + address -= map.Address; + + ulong addressAligned = BitUtils.AlignDown(address, AddressSpacePartition.GuestPageSize); + ulong endAddressAligned = BitUtils.AlignUp(address + size, AddressSpacePartition.GuestPageSize); + ulong sizeAligned = endAddressAligned - addressAligned; + + if (!_tracking.VirtualMemoryEvent(map.Va + addressAligned, sizeAligned, write)) + { + return 0; + } + + return _readPtCallback(map.Va + address); + } + + public override void Destroy() + { + _memoryEh.Dispose(); + + base.Destroy(); + } + } + + private readonly MemoryTracking _tracking; + private readonly Func _readPtCallback; + private readonly object _lock; + + public AddressSpacePartitionAllocator( + MemoryTracking tracking, + Func readPtCallback, + object locker) : base(DefaultBlockAlignment, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible) + { + _tracking = tracking; + _readPtCallback = readPtCallback; + _lock = locker; + } + + public AddressSpacePartitionAllocation Allocate(ulong va, ulong size) + { + AddressSpacePartitionAllocation allocation = new(this, Allocate(size, MemoryBlock.GetPageSize(), CreateBlock)); + allocation.RegisterMapping(va, va + size); + + return allocation; + } + + private Block CreateBlock(MemoryBlock memory, ulong size) + { + return new Block(_tracking, _readPtCallback, memory, size, _lock); + } + } +} diff --git a/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitionMultiAllocation.cs b/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitionMultiAllocation.cs new file mode 100644 index 0000000000..3b065583f8 --- /dev/null +++ b/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitionMultiAllocation.cs @@ -0,0 +1,101 @@ +using Ryujinx.Memory; +using System; +using System.Diagnostics; + +namespace Ryujinx.Cpu.Jit.HostTracked +{ + class AddressSpacePartitionMultiAllocation : IDisposable + { + private readonly AddressSpacePartitionAllocation _baseMemory; + private AddressSpacePartitionAllocation _baseMemoryRo; + private AddressSpacePartitionAllocation _baseMemoryNone; + + public AddressSpacePartitionMultiAllocation(AddressSpacePartitionAllocation baseMemory) + { + _baseMemory = baseMemory; + } + + public void MapView(MemoryBlock srcBlock, ulong srcOffset, ulong dstOffset, ulong size) + { + _baseMemory.MapView(srcBlock, srcOffset, dstOffset, size); + + if (_baseMemoryRo.IsValid) + { + _baseMemoryRo.MapView(srcBlock, srcOffset, dstOffset, size); + _baseMemoryRo.Reprotect(dstOffset, size, MemoryPermission.Read, false); + } + } + + public void LateMapView(MemoryBlock srcBlock, ulong srcOffset, ulong dstOffset, ulong size) + { + _baseMemoryRo.MapView(srcBlock, srcOffset, dstOffset, size); + _baseMemoryRo.Reprotect(dstOffset, size, MemoryPermission.Read, false); + } + + public void UnmapView(MemoryBlock srcBlock, ulong offset, ulong size) + { + _baseMemory.UnmapView(srcBlock, offset, size); + + if (_baseMemoryRo.IsValid) + { + _baseMemoryRo.UnmapView(srcBlock, offset, size); + } + } + + public void Reprotect(ulong offset, ulong size, MemoryPermission permission, bool throwOnFail) + { + _baseMemory.Reprotect(offset, size, permission, throwOnFail); + } + + public IntPtr GetPointer(ulong offset, ulong size) + { + return _baseMemory.GetPointer(offset, size); + } + + public bool LazyInitMirrorForProtection(AddressSpacePartitioned addressSpace, ulong blockAddress, ulong blockSize, MemoryPermission permission) + { + if (permission == MemoryPermission.None && !_baseMemoryNone.IsValid) + { + _baseMemoryNone = addressSpace.CreateAsPartitionAllocation(blockAddress, blockSize); + } + else if (permission == MemoryPermission.Read && !_baseMemoryRo.IsValid) + { + _baseMemoryRo = addressSpace.CreateAsPartitionAllocation(blockAddress, blockSize); + + return true; + } + + return false; + } + + public IntPtr GetPointerForProtection(ulong offset, ulong size, MemoryPermission permission) + { + AddressSpacePartitionAllocation allocation = permission switch + { + MemoryPermission.ReadAndWrite => _baseMemory, + MemoryPermission.Read => _baseMemoryRo, + MemoryPermission.None => _baseMemoryNone, + _ => throw new ArgumentException($"Invalid protection \"{permission}\"."), + }; + + Debug.Assert(allocation.IsValid); + + return allocation.GetPointer(offset, size); + } + + public void Dispose() + { + _baseMemory.Dispose(); + + if (_baseMemoryRo.IsValid) + { + _baseMemoryRo.Dispose(); + } + + if (_baseMemoryNone.IsValid) + { + _baseMemoryNone.Dispose(); + } + } + } +} diff --git a/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitioned.cs b/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitioned.cs new file mode 100644 index 0000000000..2cf2c248b2 --- /dev/null +++ b/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitioned.cs @@ -0,0 +1,407 @@ +using Ryujinx.Common; +using Ryujinx.Memory; +using Ryujinx.Memory.Tracking; +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Ryujinx.Cpu.Jit.HostTracked +{ + class AddressSpacePartitioned : IDisposable + { + private const int PartitionBits = 25; + private const ulong PartitionSize = 1UL << PartitionBits; + + private readonly MemoryBlock _backingMemory; + private readonly List _partitions; + private readonly AddressSpacePartitionAllocator _asAllocator; + private readonly Action _updatePtCallback; + private readonly bool _useProtectionMirrors; + + public AddressSpacePartitioned(MemoryTracking tracking, MemoryBlock backingMemory, NativePageTable nativePageTable, bool useProtectionMirrors) + { + _backingMemory = backingMemory; + _partitions = new(); + _asAllocator = new(tracking, nativePageTable.Read, _partitions); + _updatePtCallback = nativePageTable.Update; + _useProtectionMirrors = useProtectionMirrors; + } + + public void Map(ulong va, ulong pa, ulong size) + { + ulong endVa = va + size; + + lock (_partitions) + { + EnsurePartitionsLocked(va, size); + + while (va < endVa) + { + int partitionIndex = FindPartitionIndexLocked(va); + AddressSpacePartition partition = _partitions[partitionIndex]; + + (ulong clampedVa, ulong clampedEndVa) = ClampRange(partition, va, endVa); + + partition.Map(clampedVa, pa, clampedEndVa - clampedVa); + + ulong currentSize = clampedEndVa - clampedVa; + + va += currentSize; + pa += currentSize; + + InsertOrRemoveBridgeIfNeeded(partitionIndex); + } + } + } + + public void Unmap(ulong va, ulong size) + { + ulong endVa = va + size; + + while (va < endVa) + { + AddressSpacePartition partition; + + lock (_partitions) + { + int partitionIndex = FindPartitionIndexLocked(va); + if (partitionIndex < 0) + { + va += PartitionSize - (va & (PartitionSize - 1)); + + continue; + } + + partition = _partitions[partitionIndex]; + + (ulong clampedVa, ulong clampedEndVa) = ClampRange(partition, va, endVa); + + partition.Unmap(clampedVa, clampedEndVa - clampedVa); + + va += clampedEndVa - clampedVa; + + InsertOrRemoveBridgeIfNeeded(partitionIndex); + + if (partition.IsEmpty()) + { + _partitions.Remove(partition); + partition.Dispose(); + } + } + } + } + + public void Reprotect(ulong va, ulong size, MemoryPermission protection) + { + ulong endVa = va + size; + + lock (_partitions) + { + while (va < endVa) + { + AddressSpacePartition partition = FindPartitionWithIndex(va, out int partitionIndex); + + if (partition == null) + { + va += PartitionSize - (va & (PartitionSize - 1)); + + continue; + } + + (ulong clampedVa, ulong clampedEndVa) = ClampRange(partition, va, endVa); + + if (_useProtectionMirrors) + { + partition.Reprotect(clampedVa, clampedEndVa - clampedVa, protection, this, _updatePtCallback); + } + else + { + partition.ReprotectAligned(clampedVa, clampedEndVa - clampedVa, protection); + + if (clampedVa == partition.Address && + partitionIndex > 0 && + _partitions[partitionIndex - 1].EndAddress == partition.Address) + { + _partitions[partitionIndex - 1].ReprotectBridge(protection); + } + } + + va += clampedEndVa - clampedVa; + } + } + } + + public PrivateRange GetPrivateAllocation(ulong va) + { + AddressSpacePartition partition = FindPartition(va); + + if (partition == null) + { + return PrivateRange.Empty; + } + + return partition.GetPrivateAllocation(va); + } + + public PrivateRange GetFirstPrivateAllocation(ulong va, ulong size, out ulong nextVa) + { + AddressSpacePartition partition = FindPartition(va); + + if (partition == null) + { + nextVa = (va & ~(PartitionSize - 1)) + PartitionSize; + + return PrivateRange.Empty; + } + + return partition.GetFirstPrivateAllocation(va, size, out nextVa); + } + + public bool HasAnyPrivateAllocation(ulong va, ulong size, out PrivateRange range) + { + range = PrivateRange.Empty; + + ulong startVa = va; + ulong endVa = va + size; + + while (va < endVa) + { + AddressSpacePartition partition = FindPartition(va); + + if (partition == null) + { + va += PartitionSize - (va & (PartitionSize - 1)); + + continue; + } + + (ulong clampedVa, ulong clampedEndVa) = ClampRange(partition, va, endVa); + + if (partition.HasPrivateAllocation(clampedVa, clampedEndVa - clampedVa, startVa, size, ref range)) + { + return true; + } + + va += clampedEndVa - clampedVa; + } + + return false; + } + + private void InsertOrRemoveBridgeIfNeeded(int partitionIndex) + { + if (partitionIndex > 0) + { + if (_partitions[partitionIndex - 1].EndAddress == _partitions[partitionIndex].Address) + { + _partitions[partitionIndex - 1].InsertBridgeAtEnd(_partitions[partitionIndex], _useProtectionMirrors); + } + else + { + _partitions[partitionIndex - 1].InsertBridgeAtEnd(null, _useProtectionMirrors); + } + } + + if (partitionIndex + 1 < _partitions.Count && _partitions[partitionIndex].EndAddress == _partitions[partitionIndex + 1].Address) + { + _partitions[partitionIndex].InsertBridgeAtEnd(_partitions[partitionIndex + 1], _useProtectionMirrors); + } + else + { + _partitions[partitionIndex].InsertBridgeAtEnd(null, _useProtectionMirrors); + } + } + + public IntPtr GetPointer(ulong va, ulong size) + { + AddressSpacePartition partition = FindPartition(va); + + return partition.GetPointer(va, size); + } + + private static (ulong, ulong) ClampRange(AddressSpacePartition partition, ulong va, ulong endVa) + { + if (va < partition.Address) + { + va = partition.Address; + } + + if (endVa > partition.EndAddress) + { + endVa = partition.EndAddress; + } + + return (va, endVa); + } + + private AddressSpacePartition FindPartition(ulong va) + { + lock (_partitions) + { + int index = FindPartitionIndexLocked(va); + if (index >= 0) + { + return _partitions[index]; + } + } + + return null; + } + + private AddressSpacePartition FindPartitionWithIndex(ulong va, out int index) + { + lock (_partitions) + { + index = FindPartitionIndexLocked(va); + if (index >= 0) + { + return _partitions[index]; + } + } + + return null; + } + + private int FindPartitionIndexLocked(ulong va) + { + int left = 0; + int middle; + int right = _partitions.Count - 1; + + while (left <= right) + { + middle = left + ((right - left) >> 1); + + AddressSpacePartition partition = _partitions[middle]; + + if (partition.Address <= va && partition.EndAddress > va) + { + return middle; + } + + if (partition.Address >= va) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + return -1; + } + + private void EnsurePartitionsLocked(ulong va, ulong size) + { + ulong endVa = BitUtils.AlignUp(va + size, PartitionSize); + va = BitUtils.AlignDown(va, PartitionSize); + + for (int i = 0; i < _partitions.Count && va < endVa; i++) + { + AddressSpacePartition partition = _partitions[i]; + + if (partition.Address <= va && partition.EndAddress > va) + { + if (partition.EndAddress >= endVa) + { + // Fully mapped already. + va = endVa; + + break; + } + + ulong gapSize; + + if (i + 1 < _partitions.Count) + { + AddressSpacePartition nextPartition = _partitions[i + 1]; + + if (partition.EndAddress == nextPartition.Address) + { + va = partition.EndAddress; + + continue; + } + + gapSize = Math.Min(endVa, nextPartition.Address) - partition.EndAddress; + } + else + { + gapSize = endVa - partition.EndAddress; + } + + _partitions.Insert(i + 1, CreateAsPartition(partition.EndAddress, gapSize)); + va = partition.EndAddress + gapSize; + i++; + } + else if (partition.EndAddress > va) + { + Debug.Assert(partition.Address > va); + + ulong gapSize; + + if (partition.Address < endVa) + { + gapSize = partition.Address - va; + } + else + { + gapSize = endVa - va; + } + + _partitions.Insert(i, CreateAsPartition(va, gapSize)); + va = Math.Min(partition.EndAddress, endVa); + i++; + } + } + + if (va < endVa) + { + _partitions.Add(CreateAsPartition(va, endVa - va)); + } + + ValidatePartitionList(); + } + + [Conditional("DEBUG")] + private void ValidatePartitionList() + { + for (int i = 1; i < _partitions.Count; i++) + { + Debug.Assert(_partitions[i].Address > _partitions[i - 1].Address); + Debug.Assert(_partitions[i].EndAddress > _partitions[i - 1].EndAddress); + } + } + + private AddressSpacePartition CreateAsPartition(ulong va, ulong size) + { + return new(CreateAsPartitionAllocation(va, size), _backingMemory, va, size); + } + + public AddressSpacePartitionAllocation CreateAsPartitionAllocation(ulong va, ulong size) + { + return _asAllocator.Allocate(va, size + MemoryBlock.GetPageSize()); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + foreach (AddressSpacePartition partition in _partitions) + { + partition.Dispose(); + } + + _partitions.Clear(); + _asAllocator.Dispose(); + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Cpu/Jit/HostTracked/NativePageTable.cs b/src/Ryujinx.Cpu/Jit/HostTracked/NativePageTable.cs new file mode 100644 index 0000000000..e3174e3fc5 --- /dev/null +++ b/src/Ryujinx.Cpu/Jit/HostTracked/NativePageTable.cs @@ -0,0 +1,223 @@ +using Ryujinx.Cpu.Signal; +using Ryujinx.Memory; +using System; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Cpu.Jit.HostTracked +{ + sealed class NativePageTable : IDisposable + { + private delegate ulong TrackingEventDelegate(ulong address, ulong size, bool write); + + private const int PageBits = 12; + private const int PageSize = 1 << PageBits; + private const int PageMask = PageSize - 1; + + private const int PteSize = 8; + + private readonly int _bitsPerPtPage; + private readonly int _entriesPerPtPage; + private readonly int _pageCommitmentBits; + + private readonly PageTable _pageTable; + private readonly MemoryBlock _nativePageTable; + private readonly ulong[] _pageCommitmentBitmap; + private readonly ulong _hostPageSize; + + private readonly TrackingEventDelegate _trackingEvent; + + private bool _disposed; + + public IntPtr PageTablePointer => _nativePageTable.Pointer; + + public NativePageTable(ulong asSize) + { + ulong hostPageSize = MemoryBlock.GetPageSize(); + + _entriesPerPtPage = (int)(hostPageSize / sizeof(ulong)); + _bitsPerPtPage = BitOperations.Log2((uint)_entriesPerPtPage); + _pageCommitmentBits = PageBits + _bitsPerPtPage; + + _hostPageSize = hostPageSize; + _pageTable = new PageTable(); + _nativePageTable = new MemoryBlock((asSize / PageSize) * PteSize + _hostPageSize, MemoryAllocationFlags.Reserve); + _pageCommitmentBitmap = new ulong[(asSize >> _pageCommitmentBits) / (sizeof(ulong) * 8)]; + + ulong ptStart = (ulong)_nativePageTable.Pointer; + ulong ptEnd = ptStart + _nativePageTable.Size; + + _trackingEvent = VirtualMemoryEvent; + + bool added = NativeSignalHandler.AddTrackedRegion((nuint)ptStart, (nuint)ptEnd, Marshal.GetFunctionPointerForDelegate(_trackingEvent)); + + if (!added) + { + throw new InvalidOperationException("Number of allowed tracked regions exceeded."); + } + } + + public void Map(ulong va, ulong pa, ulong size, AddressSpacePartitioned addressSpace, MemoryBlock backingMemory, bool privateMap) + { + while (size != 0) + { + _pageTable.Map(va, pa); + + EnsureCommitment(va); + + if (privateMap) + { + _nativePageTable.Write((va / PageSize) * PteSize, GetPte(va, addressSpace.GetPointer(va, PageSize))); + } + else + { + _nativePageTable.Write((va / PageSize) * PteSize, GetPte(va, backingMemory.GetPointer(pa, PageSize))); + } + + va += PageSize; + pa += PageSize; + size -= PageSize; + } + } + + public void Unmap(ulong va, ulong size) + { + IntPtr guardPagePtr = GetGuardPagePointer(); + + while (size != 0) + { + _pageTable.Unmap(va); + _nativePageTable.Write((va / PageSize) * PteSize, GetPte(va, guardPagePtr)); + + va += PageSize; + size -= PageSize; + } + } + + public ulong Read(ulong va) + { + ulong pte = _nativePageTable.Read((va / PageSize) * PteSize); + + pte += va & ~(ulong)PageMask; + + return pte + (va & PageMask); + } + + public void Update(ulong va, IntPtr ptr, ulong size) + { + ulong remainingSize = size; + + while (remainingSize != 0) + { + EnsureCommitment(va); + + _nativePageTable.Write((va / PageSize) * PteSize, GetPte(va, ptr)); + + va += PageSize; + ptr += PageSize; + remainingSize -= PageSize; + } + } + + private void EnsureCommitment(ulong va) + { + ulong bit = va >> _pageCommitmentBits; + + int index = (int)(bit / (sizeof(ulong) * 8)); + int shift = (int)(bit % (sizeof(ulong) * 8)); + + ulong mask = 1UL << shift; + + ulong oldMask = _pageCommitmentBitmap[index]; + + if ((oldMask & mask) == 0) + { + lock (_pageCommitmentBitmap) + { + oldMask = _pageCommitmentBitmap[index]; + + if ((oldMask & mask) != 0) + { + return; + } + + _nativePageTable.Commit(bit * _hostPageSize, _hostPageSize); + + Span pageSpan = MemoryMarshal.Cast(_nativePageTable.GetSpan(bit * _hostPageSize, (int)_hostPageSize)); + + Debug.Assert(pageSpan.Length == _entriesPerPtPage); + + IntPtr guardPagePtr = GetGuardPagePointer(); + + for (int i = 0; i < pageSpan.Length; i++) + { + pageSpan[i] = GetPte((bit << _pageCommitmentBits) | ((ulong)i * PageSize), guardPagePtr); + } + + _pageCommitmentBitmap[index] = oldMask | mask; + } + } + } + + private IntPtr GetGuardPagePointer() + { + return _nativePageTable.GetPointer(_nativePageTable.Size - _hostPageSize, _hostPageSize); + } + + private static ulong GetPte(ulong va, IntPtr ptr) + { + Debug.Assert((va & PageMask) == 0); + + return (ulong)ptr - va; + } + + public ulong GetPhysicalAddress(ulong va) + { + return _pageTable.Read(va) + (va & PageMask); + } + + private ulong VirtualMemoryEvent(ulong address, ulong size, bool write) + { + if (address < _nativePageTable.Size - _hostPageSize) + { + // Some prefetch instructions do not cause faults with invalid addresses. + // Retry if we are hitting a case where the page table is unmapped, the next + // run will execute the actual instruction. + // The address loaded from the page table will be invalid, and it should hit the else case + // if the instruction faults on unmapped or protected memory. + + ulong va = address * (PageSize / sizeof(ulong)); + + EnsureCommitment(va); + + return (ulong)_nativePageTable.Pointer + address; + } + else + { + throw new InvalidMemoryRegionException(); + } + } + + private void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + NativeSignalHandler.RemoveTrackedRegion((nuint)_nativePageTable.Pointer); + + _nativePageTable.Dispose(); + } + + _disposed = true; + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Cpu/Jit/JitCpuContext.cs b/src/Ryujinx.Cpu/Jit/JitCpuContext.cs index dce0490a41..9893c59b29 100644 --- a/src/Ryujinx.Cpu/Jit/JitCpuContext.cs +++ b/src/Ryujinx.Cpu/Jit/JitCpuContext.cs @@ -15,9 +15,9 @@ namespace Ryujinx.Cpu.Jit _tickSource = tickSource; _translator = new Translator(new JitMemoryAllocator(forJit: true), memory, for64Bit); - if (memory.Type.IsHostMapped()) + if (memory.Type.IsHostMappedOrTracked()) { - NativeSignalHandler.InitializeSignalHandler(MemoryBlock.GetPageSize()); + NativeSignalHandler.InitializeSignalHandler(); } memory.UnmapEvent += UnmapHandler; diff --git a/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs b/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs new file mode 100644 index 0000000000..18404bcc74 --- /dev/null +++ b/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs @@ -0,0 +1,627 @@ +using ARMeilleure.Memory; +using Ryujinx.Cpu.Jit.HostTracked; +using Ryujinx.Cpu.Signal; +using Ryujinx.Memory; +using Ryujinx.Memory.Range; +using Ryujinx.Memory.Tracking; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Cpu.Jit +{ + /// + /// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region. + /// + public sealed class MemoryManagerHostTracked : VirtualMemoryManagerRefCountedBase, IWritableBlock, IMemoryManager, IVirtualMemoryManagerTracked + { + private readonly InvalidAccessHandler _invalidAccessHandler; + private readonly bool _unsafeMode; + + private readonly MemoryBlock _backingMemory; + + public int AddressSpaceBits { get; } + + public MemoryTracking Tracking { get; } + + private readonly NativePageTable _nativePageTable; + private readonly AddressSpacePartitioned _addressSpace; + + private readonly ManagedPageFlags _pages; + + protected override ulong AddressSpaceSize { get; } + + /// + public bool Supports4KBPages => false; + + public IntPtr PageTablePointer => _nativePageTable.PageTablePointer; + + public MemoryManagerType Type => _unsafeMode ? MemoryManagerType.HostTrackedUnsafe : MemoryManagerType.HostTracked; + + public event Action UnmapEvent; + + /// + /// Creates a new instance of the host tracked memory manager. + /// + /// Physical backing memory where virtual memory will be mapped to + /// Size of the address space + /// True if unmanaged access should not be masked (unsafe), false otherwise. + /// Optional function to handle invalid memory accesses + public MemoryManagerHostTracked(MemoryBlock backingMemory, ulong addressSpaceSize, bool unsafeMode, InvalidAccessHandler invalidAccessHandler) + { + bool useProtectionMirrors = MemoryBlock.GetPageSize() > PageSize; + + Tracking = new MemoryTracking(this, PageSize, invalidAccessHandler, useProtectionMirrors); + + _backingMemory = backingMemory; + _invalidAccessHandler = invalidAccessHandler; + _unsafeMode = unsafeMode; + AddressSpaceSize = addressSpaceSize; + + ulong asSize = PageSize; + int asBits = PageBits; + + while (asSize < AddressSpaceSize) + { + asSize <<= 1; + asBits++; + } + + AddressSpaceBits = asBits; + + if (useProtectionMirrors && !NativeSignalHandler.SupportsFaultAddressPatching()) + { + // Currently we require being able to change the fault address to something else + // in order to "emulate" 4KB granularity protection on systems with larger page size. + + throw new PlatformNotSupportedException(); + } + + _pages = new ManagedPageFlags(asBits); + _nativePageTable = new(asSize); + _addressSpace = new(Tracking, backingMemory, _nativePageTable, useProtectionMirrors); + } + + /// + public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags) + { + AssertValidAddressAndSize(va, size); + + if (flags.HasFlag(MemoryMapFlags.Private)) + { + _addressSpace.Map(va, pa, size); + } + + _pages.AddMapping(va, size); + _nativePageTable.Map(va, pa, size, _addressSpace, _backingMemory, flags.HasFlag(MemoryMapFlags.Private)); + + Tracking.Map(va, size); + } + + /// + public void MapForeign(ulong va, nuint hostPointer, ulong size) + { + throw new NotSupportedException(); + } + + /// + public void Unmap(ulong va, ulong size) + { + AssertValidAddressAndSize(va, size); + + _addressSpace.Unmap(va, size); + + UnmapEvent?.Invoke(va, size); + Tracking.Unmap(va, size); + + _pages.RemoveMapping(va, size); + _nativePageTable.Unmap(va, size); + } + + public T Read(ulong va) where T : unmanaged + { + return MemoryMarshal.Cast(GetSpan(va, Unsafe.SizeOf()))[0]; + } + + public T ReadTracked(ulong va) where T : unmanaged + { + try + { + SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), false); + + return Read(va); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + + return default; + } + } + + public override void Read(ulong va, Span data) + { + ReadImpl(va, data); + } + + public void Write(ulong va, T value) where T : unmanaged + { + Write(va, MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1))); + } + + public void Write(ulong va, ReadOnlySpan data) + { + if (data.Length == 0) + { + return; + } + + SignalMemoryTracking(va, (ulong)data.Length, true); + + WriteImpl(va, data); + } + + public void WriteUntracked(ulong va, ReadOnlySpan data) + { + if (data.Length == 0) + { + return; + } + + WriteImpl(va, data); + } + + public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data) + { + if (data.Length == 0) + { + return false; + } + + SignalMemoryTracking(va, (ulong)data.Length, false); + + if (TryGetVirtualContiguous(va, data.Length, out MemoryBlock memoryBlock, out ulong offset)) + { + var target = memoryBlock.GetSpan(offset, data.Length); + + bool changed = !data.SequenceEqual(target); + + if (changed) + { + data.CopyTo(target); + } + + return changed; + } + else + { + WriteImpl(va, data); + + return true; + } + } + + private void WriteImpl(ulong va, ReadOnlySpan data) + { + try + { + AssertValidAddressAndSize(va, (ulong)data.Length); + + ulong endVa = va + (ulong)data.Length; + int offset = 0; + + while (va < endVa) + { + (MemoryBlock memory, ulong rangeOffset, ulong copySize) = GetMemoryOffsetAndSize(va, (ulong)(data.Length - offset)); + + data.Slice(offset, (int)copySize).CopyTo(memory.GetSpan(rangeOffset, (int)copySize)); + + va += copySize; + offset += (int)copySize; + } + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + } + } + + public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) + { + if (size == 0) + { + return ReadOnlySpan.Empty; + } + + if (tracked) + { + SignalMemoryTracking(va, (ulong)size, false); + } + + if (TryGetVirtualContiguous(va, size, out MemoryBlock memoryBlock, out ulong offset)) + { + return memoryBlock.GetSpan(offset, size); + } + else + { + Span data = new byte[size]; + + ReadImpl(va, data); + + return data; + } + } + + public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) + { + if (size == 0) + { + return new WritableRegion(null, va, Memory.Empty); + } + + if (tracked) + { + SignalMemoryTracking(va, (ulong)size, true); + } + + if (TryGetVirtualContiguous(va, size, out MemoryBlock memoryBlock, out ulong offset)) + { + return new WritableRegion(null, va, memoryBlock.GetMemory(offset, size)); + } + else + { + Memory memory = new byte[size]; + + ReadImpl(va, memory.Span); + + return new WritableRegion(this, va, memory); + } + } + + public ref T GetRef(ulong va) where T : unmanaged + { + if (!TryGetVirtualContiguous(va, Unsafe.SizeOf(), out MemoryBlock memory, out ulong offset)) + { + ThrowMemoryNotContiguous(); + } + + SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), true); + + return ref memory.GetRef(offset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsMapped(ulong va) + { + return ValidateAddress(va) && _pages.IsMapped(va); + } + + public bool IsRangeMapped(ulong va, ulong size) + { + AssertValidAddressAndSize(va, size); + + return _pages.IsRangeMapped(va, size); + } + + private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException(); + + private bool TryGetVirtualContiguous(ulong va, int size, out MemoryBlock memory, out ulong offset) + { + if (_addressSpace.HasAnyPrivateAllocation(va, (ulong)size, out PrivateRange range)) + { + // If we have a private allocation overlapping the range, + // then the access is only considered contiguous if it covers the entire range. + + if (range.Memory != null) + { + memory = range.Memory; + offset = range.Offset; + + return true; + } + + memory = null; + offset = 0; + + return false; + } + + memory = _backingMemory; + offset = GetPhysicalAddressInternal(va); + + return IsPhysicalContiguous(va, size); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsPhysicalContiguous(ulong va, int size) + { + if (!ValidateAddress(va) || !ValidateAddressAndSize(va, (ulong)size)) + { + return false; + } + + int pages = GetPagesCount(va, (uint)size, out va); + + for (int page = 0; page < pages - 1; page++) + { + if (!ValidateAddress(va + PageSize)) + { + return false; + } + + if (GetPhysicalAddressInternal(va) + PageSize != GetPhysicalAddressInternal(va + PageSize)) + { + return false; + } + + va += PageSize; + } + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ulong GetContiguousSize(ulong va, ulong size) + { + ulong contiguousSize = PageSize - (va & PageMask); + + if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size)) + { + return contiguousSize; + } + + int pages = GetPagesCount(va, size, out va); + + for (int page = 0; page < pages - 1; page++) + { + if (!ValidateAddress(va + PageSize)) + { + return contiguousSize; + } + + if (GetPhysicalAddressInternal(va) + PageSize != GetPhysicalAddressInternal(va + PageSize)) + { + return contiguousSize; + } + + va += PageSize; + contiguousSize += PageSize; + } + + return Math.Min(contiguousSize, size); + } + + private (MemoryBlock, ulong, ulong) GetMemoryOffsetAndSize(ulong va, ulong size) + { + PrivateRange privateRange = _addressSpace.GetFirstPrivateAllocation(va, size, out ulong nextVa); + + if (privateRange.Memory != null) + { + return (privateRange.Memory, privateRange.Offset, privateRange.Size); + } + + ulong physSize = GetContiguousSize(va, Math.Min(size, nextVa - va)); + + return (_backingMemory, GetPhysicalAddressChecked(va), physSize); + } + + public IEnumerable GetHostRegions(ulong va, ulong size) + { + if (!ValidateAddressAndSize(va, size)) + { + return null; + } + + var regions = new List(); + ulong endVa = va + size; + + try + { + while (va < endVa) + { + (MemoryBlock memory, ulong rangeOffset, ulong rangeSize) = GetMemoryOffsetAndSize(va, endVa - va); + + regions.Add(new((UIntPtr)memory.GetPointer(rangeOffset, rangeSize), rangeSize)); + + va += rangeSize; + } + } + catch (InvalidMemoryRegionException) + { + return null; + } + + return regions; + } + + public IEnumerable GetPhysicalRegions(ulong va, ulong size) + { + if (size == 0) + { + return Enumerable.Empty(); + } + + return GetPhysicalRegionsImpl(va, size); + } + + private List GetPhysicalRegionsImpl(ulong va, ulong size) + { + if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size)) + { + return null; + } + + int pages = GetPagesCount(va, (uint)size, out va); + + var regions = new List(); + + ulong regionStart = GetPhysicalAddressInternal(va); + ulong regionSize = PageSize; + + for (int page = 0; page < pages - 1; page++) + { + if (!ValidateAddress(va + PageSize)) + { + return null; + } + + ulong newPa = GetPhysicalAddressInternal(va + PageSize); + + if (GetPhysicalAddressInternal(va) + PageSize != newPa) + { + regions.Add(new MemoryRange(regionStart, regionSize)); + regionStart = newPa; + regionSize = 0; + } + + va += PageSize; + regionSize += PageSize; + } + + regions.Add(new MemoryRange(regionStart, regionSize)); + + return regions; + } + + private void ReadImpl(ulong va, Span data) + { + if (data.Length == 0) + { + return; + } + + try + { + AssertValidAddressAndSize(va, (ulong)data.Length); + + ulong endVa = va + (ulong)data.Length; + int offset = 0; + + while (va < endVa) + { + (MemoryBlock memory, ulong rangeOffset, ulong copySize) = GetMemoryOffsetAndSize(va, (ulong)(data.Length - offset)); + + memory.GetSpan(rangeOffset, (int)copySize).CopyTo(data.Slice(offset, (int)copySize)); + + va += copySize; + offset += (int)copySize; + } + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + } + } + + /// + /// + /// This function also validates that the given range is both valid and mapped, and will throw if it is not. + /// + public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) + { + AssertValidAddressAndSize(va, size); + + if (precise) + { + Tracking.VirtualMemoryEvent(va, size, write, precise: true, exemptId); + return; + } + + // Software table, used for managed memory tracking. + + _pages.SignalMemoryTracking(Tracking, va, size, write, exemptId); + } + + /// + /// Computes the number of pages in a virtual address range. + /// + /// Virtual address of the range + /// Size of the range + /// The virtual address of the beginning of the first page + /// This function does not differentiate between allocated and unallocated pages. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetPagesCount(ulong va, ulong size, out ulong startVa) + { + // WARNING: Always check if ulong does not overflow during the operations. + startVa = va & ~(ulong)PageMask; + ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask; + + return (int)(vaSpan / PageSize); + } + + public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None) + { + return Tracking.BeginTracking(address, size, id, flags); + } + + public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None) + { + return Tracking.BeginGranularTracking(address, size, handles, granularity, id, flags); + } + + public SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id) + { + return Tracking.BeginSmartGranularTracking(address, size, granularity, id); + } + + private ulong GetPhysicalAddressChecked(ulong va) + { + if (!IsMapped(va)) + { + ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}"); + } + + return GetPhysicalAddressInternal(va); + } + + private ulong GetPhysicalAddressInternal(ulong va) + { + return _nativePageTable.GetPhysicalAddress(va); + } + + /// + public void Reprotect(ulong va, ulong size, MemoryPermission protection) + { + // TODO + } + + /// + public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest) + { + if (guest) + { + _addressSpace.Reprotect(va, size, protection); + } + else + { + _pages.TrackingReprotect(va, size, protection); + } + } + + /// + /// Disposes of resources used by the memory manager. + /// + protected override void Destroy() + { + _addressSpace.Dispose(); + _nativePageTable.Dispose(); + } + + protected override Span GetPhysicalAddressSpan(ulong pa, int size) + => _backingMemory.GetSpan(pa, size); + + protected override ulong TranslateVirtualAddressForRead(ulong va) + => GetPhysicalAddressInternal(va); + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMemory.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMemory.cs index 6ab4b94953..d8caee6e74 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMemory.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMemory.cs @@ -1126,11 +1126,23 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 Operand destination64 = new(destination.Kind, OperandType.I64, destination.Value); Operand basePointer = new(regAlloc.FixedPageTableRegister, RegisterType.Integer, OperandType.I64); - if (mmType == MemoryManagerType.HostMapped || mmType == MemoryManagerType.HostMappedUnsafe) - { - // We don't need to mask the address for the safe mode, since it is already naturally limited to 32-bit - // and can never reach out of the guest address space. + // We don't need to mask the address for the safe mode, since it is already naturally limited to 32-bit + // and can never reach out of the guest address space. + if (mmType.IsHostTracked()) + { + int tempRegister = regAlloc.AllocateTempGprRegister(); + + Operand pte = new(tempRegister, RegisterType.Integer, OperandType.I64); + + asm.Lsr(pte, guestAddress, new Operand(OperandKind.Constant, OperandType.I32, 12)); + asm.LdrRr(pte, basePointer, pte, ArmExtensionType.Uxtx, true); + asm.Add(destination64, pte, guestAddress); + + regAlloc.FreeTempGprRegister(tempRegister); + } + else if (mmType.IsHostMapped()) + { asm.Add(destination64, basePointer, guestAddress); } else diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/InstName.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/InstName.cs index 3656406453..3391a2c145 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm64/InstName.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/InstName.cs @@ -1131,5 +1131,37 @@ namespace Ryujinx.Cpu.LightningJit.Arm64 return false; } + + public static bool IsPartialRegisterUpdateMemory(this InstName name) + { + switch (name) + { + case InstName.Ld1AdvsimdSnglAsNoPostIndex: + case InstName.Ld1AdvsimdSnglAsPostIndex: + case InstName.Ld2AdvsimdSnglAsNoPostIndex: + case InstName.Ld2AdvsimdSnglAsPostIndex: + case InstName.Ld3AdvsimdSnglAsNoPostIndex: + case InstName.Ld3AdvsimdSnglAsPostIndex: + case InstName.Ld4AdvsimdSnglAsNoPostIndex: + case InstName.Ld4AdvsimdSnglAsPostIndex: + return true; + } + + return false; + } + + public static bool IsPrefetchMemory(this InstName name) + { + switch (name) + { + case InstName.PrfmImm: + case InstName.PrfmLit: + case InstName.PrfmReg: + case InstName.Prfum: + return true; + } + + return false; + } } } diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterAllocator.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterAllocator.cs index c9a932093d..1c6eab0de2 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterAllocator.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterAllocator.cs @@ -1,15 +1,12 @@ +using ARMeilleure.Memory; using Ryujinx.Cpu.LightningJit.CodeGen.Arm64; using System; -using System.Diagnostics; using System.Numerics; namespace Ryujinx.Cpu.LightningJit.Arm64 { class RegisterAllocator { - public const int MaxTemps = 1; - public const int MaxTempsInclFixed = MaxTemps + 2; - private uint _gprMask; private readonly uint _fpSimdMask; private readonly uint _pStateMask; @@ -25,7 +22,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64 public uint AllFpSimdMask => _fpSimdMask; public uint AllPStateMask => _pStateMask; - public RegisterAllocator(uint gprMask, uint fpSimdMask, uint pStateMask, bool hasHostCall) + public RegisterAllocator(MemoryManagerType mmType, uint gprMask, uint fpSimdMask, uint pStateMask, bool hasHostCall) { _gprMask = gprMask; _fpSimdMask = fpSimdMask; @@ -56,7 +53,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64 BuildRegisterMap(_registerMap); - Span tempRegisters = stackalloc int[MaxTemps]; + Span tempRegisters = stackalloc int[CalculateMaxTemps(mmType)]; for (int index = 0; index < tempRegisters.Length; index++) { @@ -150,5 +147,15 @@ namespace Ryujinx.Cpu.LightningJit.Arm64 { mask &= ~(1u << index); } + + public static int CalculateMaxTemps(MemoryManagerType mmType) + { + return mmType.IsHostMapped() ? 1 : 2; + } + + public static int CalculateMaxTempsInclFixed(MemoryManagerType mmType) + { + return CalculateMaxTemps(mmType) + 2; + } } } diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterUtils.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterUtils.cs index eb3fc229fe..191e03e7b1 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterUtils.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterUtils.cs @@ -247,7 +247,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64 } } - if (!flags.HasFlag(InstFlags.ReadRt)) + if (!flags.HasFlag(InstFlags.ReadRt) || name.IsPartialRegisterUpdateMemory()) { if (flags.HasFlag(InstFlags.Rt)) { @@ -281,7 +281,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64 gprMask |= MaskFromIndex(ExtractRd(flags, encoding)); } - if (!flags.HasFlag(InstFlags.ReadRt)) + if (!flags.HasFlag(InstFlags.ReadRt) || name.IsPartialRegisterUpdateMemory()) { if (flags.HasFlag(InstFlags.Rt)) { diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Compiler.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Compiler.cs index 7ef3bf49b9..7a6d761e8d 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Compiler.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Compiler.cs @@ -316,7 +316,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64 uint pStateUseMask = multiBlock.GlobalUseMask.PStateMask; CodeWriter writer = new(); - RegisterAllocator regAlloc = new(gprUseMask, fpSimdUseMask, pStateUseMask, multiBlock.HasHostCall); + RegisterAllocator regAlloc = new(memoryManager.Type, gprUseMask, fpSimdUseMask, pStateUseMask, multiBlock.HasHostCall); RegisterSaveRestore rsr = new( regAlloc.AllGprMask & AbiConstants.GprCalleeSavedRegsMask, regAlloc.AllFpSimdMask & AbiConstants.FpSimdCalleeSavedRegsMask, diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Decoder.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Decoder.cs index 00a1758f29..d5e1eb19c2 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Decoder.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Decoder.cs @@ -274,7 +274,8 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64 uint tempGprUseMask = gprUseMask | instGprReadMask | instGprWriteMask; - if (CalculateAvailableTemps(tempGprUseMask) < CalculateRequiredGprTemps(tempGprUseMask) || totalInsts++ >= MaxInstructionsPerFunction) + if (CalculateAvailableTemps(tempGprUseMask) < CalculateRequiredGprTemps(memoryManager.Type, tempGprUseMask) || + totalInsts++ >= MaxInstructionsPerFunction) { isTruncated = true; address -= 4UL; @@ -378,9 +379,9 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64 return false; } - private static int CalculateRequiredGprTemps(uint gprUseMask) + private static int CalculateRequiredGprTemps(MemoryManagerType mmType, uint gprUseMask) { - return BitOperations.PopCount(gprUseMask & RegisterUtils.ReservedRegsMask) + RegisterAllocator.MaxTempsInclFixed; + return BitOperations.PopCount(gprUseMask & RegisterUtils.ReservedRegsMask) + RegisterAllocator.CalculateMaxTempsInclFixed(mmType); } private static int CalculateAvailableTemps(uint gprUseMask) diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitMemory.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitMemory.cs index e03d9373a1..790a7de95b 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitMemory.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitMemory.cs @@ -55,6 +55,16 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64 ulong pc, uint encoding) { + if (name.IsPrefetchMemory() && mmType == MemoryManagerType.HostTrackedUnsafe) + { + // Prefetch to invalid addresses do not cause faults, so for memory manager + // types where we need to access the page table before doing the prefetch, + // we should make sure we won't try to access an out of bounds page table region. + // To do this, we force the masked memory manager variant to be used. + + mmType = MemoryManagerType.HostTracked; + } + switch (addressForm) { case AddressForm.OffsetReg: @@ -511,18 +521,48 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64 WriteAddressTranslation(asBits, mmType, regAlloc, ref asm, destination, guestAddress); } - private static void WriteAddressTranslation(int asBits, MemoryManagerType mmType, RegisterAllocator regAlloc, ref Assembler asm, Operand destination, ulong guestAddress) + private static void WriteAddressTranslation( + int asBits, + MemoryManagerType mmType, + RegisterAllocator regAlloc, + ref Assembler asm, + Operand destination, + ulong guestAddress) { asm.Mov(destination, guestAddress); WriteAddressTranslation(asBits, mmType, regAlloc, ref asm, destination, destination); } - private static void WriteAddressTranslation(int asBits, MemoryManagerType mmType, RegisterAllocator regAlloc, ref Assembler asm, Operand destination, Operand guestAddress) + private static void WriteAddressTranslation( + int asBits, + MemoryManagerType mmType, + RegisterAllocator regAlloc, + ref Assembler asm, + Operand destination, + Operand guestAddress) { Operand basePointer = new(regAlloc.FixedPageTableRegister, RegisterType.Integer, OperandType.I64); - if (mmType == MemoryManagerType.HostMapped || mmType == MemoryManagerType.HostMappedUnsafe) + if (mmType.IsHostTracked()) + { + int tempRegister = regAlloc.AllocateTempGprRegister(); + + Operand pte = new(tempRegister, RegisterType.Integer, OperandType.I64); + + asm.Lsr(pte, guestAddress, new Operand(OperandKind.Constant, OperandType.I32, 12)); + + if (mmType == MemoryManagerType.HostTracked) + { + asm.And(pte, pte, new Operand(OperandKind.Constant, OperandType.I64, ulong.MaxValue >> (64 - (asBits - 12)))); + } + + asm.LdrRr(pte, basePointer, pte, ArmExtensionType.Uxtx, true); + asm.Add(destination, pte, guestAddress); + + regAlloc.FreeTempGprRegister(tempRegister); + } + else if (mmType.IsHostMapped()) { if (mmType == MemoryManagerType.HostMapped) { diff --git a/src/Ryujinx.Cpu/LightningJit/Translator.cs b/src/Ryujinx.Cpu/LightningJit/Translator.cs index c883c1d601..d624102534 100644 --- a/src/Ryujinx.Cpu/LightningJit/Translator.cs +++ b/src/Ryujinx.Cpu/LightningJit/Translator.cs @@ -68,9 +68,9 @@ namespace Ryujinx.Cpu.LightningJit FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub; - if (memory.Type.IsHostMapped()) + if (memory.Type.IsHostMappedOrTracked()) { - NativeSignalHandler.InitializeSignalHandler(MemoryBlock.GetPageSize()); + NativeSignalHandler.InitializeSignalHandler(); } } diff --git a/src/Ryujinx.Cpu/MemoryEhMeilleure.cs b/src/Ryujinx.Cpu/MemoryEhMeilleure.cs index f3a5b056bc..379ace9413 100644 --- a/src/Ryujinx.Cpu/MemoryEhMeilleure.cs +++ b/src/Ryujinx.Cpu/MemoryEhMeilleure.cs @@ -1,3 +1,4 @@ +using Ryujinx.Common; using Ryujinx.Cpu.Signal; using Ryujinx.Memory; using Ryujinx.Memory.Tracking; @@ -8,19 +9,27 @@ namespace Ryujinx.Cpu { public class MemoryEhMeilleure : IDisposable { - private delegate bool TrackingEventDelegate(ulong address, ulong size, bool write); + public delegate ulong TrackingEventDelegate(ulong address, ulong size, bool write); + private readonly MemoryTracking _tracking; private readonly TrackingEventDelegate _trackingEvent; + private readonly ulong _pageSize; + private readonly ulong _baseAddress; private readonly ulong _mirrorAddress; - public MemoryEhMeilleure(MemoryBlock addressSpace, MemoryBlock addressSpaceMirror, MemoryTracking tracking) + public MemoryEhMeilleure(MemoryBlock addressSpace, MemoryBlock addressSpaceMirror, MemoryTracking tracking, TrackingEventDelegate trackingEvent = null) { _baseAddress = (ulong)addressSpace.Pointer; + ulong endAddress = _baseAddress + addressSpace.Size; - _trackingEvent = tracking.VirtualMemoryEvent; + _tracking = tracking; + _trackingEvent = trackingEvent ?? VirtualMemoryEvent; + + _pageSize = MemoryBlock.GetPageSize(); + bool added = NativeSignalHandler.AddTrackedRegion((nuint)_baseAddress, (nuint)endAddress, Marshal.GetFunctionPointerForDelegate(_trackingEvent)); if (!added) @@ -28,7 +37,7 @@ namespace Ryujinx.Cpu throw new InvalidOperationException("Number of allowed tracked regions exceeded."); } - if (OperatingSystem.IsWindows()) + if (OperatingSystem.IsWindows() && addressSpaceMirror != null) { // Add a tracking event with no signal handler for the mirror on Windows. // The native handler has its own code to check for the partial overlap race when regions are protected by accident, @@ -46,6 +55,21 @@ namespace Ryujinx.Cpu } } + private ulong VirtualMemoryEvent(ulong address, ulong size, bool write) + { + ulong pageSize = _pageSize; + ulong addressAligned = BitUtils.AlignDown(address, pageSize); + ulong endAddressAligned = BitUtils.AlignUp(address + size, pageSize); + ulong sizeAligned = endAddressAligned - addressAligned; + + if (_tracking.VirtualMemoryEvent(addressAligned, sizeAligned, write)) + { + return _baseAddress + address; + } + + return 0; + } + public void Dispose() { GC.SuppressFinalize(this); diff --git a/src/Ryujinx.Cpu/PrivateMemoryAllocator.cs b/src/Ryujinx.Cpu/PrivateMemoryAllocator.cs index ce8e834198..8db74f1e92 100644 --- a/src/Ryujinx.Cpu/PrivateMemoryAllocator.cs +++ b/src/Ryujinx.Cpu/PrivateMemoryAllocator.cs @@ -143,7 +143,7 @@ namespace Ryujinx.Cpu } } - public PrivateMemoryAllocator(int blockAlignment, MemoryAllocationFlags allocationFlags) : base(blockAlignment, allocationFlags) + public PrivateMemoryAllocator(ulong blockAlignment, MemoryAllocationFlags allocationFlags) : base(blockAlignment, allocationFlags) { } @@ -180,10 +180,10 @@ namespace Ryujinx.Cpu private readonly List _blocks; - private readonly int _blockAlignment; + private readonly ulong _blockAlignment; private readonly MemoryAllocationFlags _allocationFlags; - public PrivateMemoryAllocatorImpl(int blockAlignment, MemoryAllocationFlags allocationFlags) + public PrivateMemoryAllocatorImpl(ulong blockAlignment, MemoryAllocationFlags allocationFlags) { _blocks = new List(); _blockAlignment = blockAlignment; @@ -212,7 +212,7 @@ namespace Ryujinx.Cpu } } - ulong blockAlignedSize = BitUtils.AlignUp(size, (ulong)_blockAlignment); + ulong blockAlignedSize = BitUtils.AlignUp(size, _blockAlignment); var memory = new MemoryBlock(blockAlignedSize, _allocationFlags); var newBlock = createBlock(memory, blockAlignedSize); diff --git a/src/Ryujinx.Cpu/Signal/NativeSignalHandler.cs b/src/Ryujinx.Cpu/Signal/NativeSignalHandler.cs index 5a9d92cc4f..93e6083298 100644 --- a/src/Ryujinx.Cpu/Signal/NativeSignalHandler.cs +++ b/src/Ryujinx.Cpu/Signal/NativeSignalHandler.cs @@ -70,7 +70,7 @@ namespace Ryujinx.Cpu.Signal config = new SignalHandlerConfig(); } - public static void InitializeSignalHandler(ulong pageSize, Func customSignalHandlerFactory = null) + public static void InitializeSignalHandler(Func customSignalHandlerFactory = null) { if (_initialized) { @@ -90,7 +90,7 @@ namespace Ryujinx.Cpu.Signal if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) { - _signalHandlerPtr = MapCode(NativeSignalHandlerGenerator.GenerateUnixSignalHandler(_handlerConfig, rangeStructSize, pageSize)); + _signalHandlerPtr = MapCode(NativeSignalHandlerGenerator.GenerateUnixSignalHandler(_handlerConfig, rangeStructSize)); if (customSignalHandlerFactory != null) { @@ -107,7 +107,7 @@ namespace Ryujinx.Cpu.Signal config.StructAddressOffset = 40; // ExceptionInformation1 config.StructWriteOffset = 32; // ExceptionInformation0 - _signalHandlerPtr = MapCode(NativeSignalHandlerGenerator.GenerateWindowsSignalHandler(_handlerConfig, rangeStructSize, pageSize)); + _signalHandlerPtr = MapCode(NativeSignalHandlerGenerator.GenerateWindowsSignalHandler(_handlerConfig, rangeStructSize)); if (customSignalHandlerFactory != null) { @@ -175,5 +175,10 @@ namespace Ryujinx.Cpu.Signal return false; } + + public static bool SupportsFaultAddressPatching() + { + return NativeSignalHandlerGenerator.SupportsFaultAddressPatchingForHost(); + } } } diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs index fb20974441..34a9a9c75f 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs @@ -1622,14 +1622,6 @@ namespace Ryujinx.Graphics.Gpu.Image /// The size of the flushing memory access public void FlushAction(TextureGroupHandle handle, ulong address, ulong size) { - // If the page size is larger than 4KB, we will have a lot of false positives for flushing. - // Let's avoid flushing textures that are unlikely to be read from CPU to improve performance - // on those platforms. - if (!_physicalMemory.Supports4KBPages && !Storage.Info.IsLinear && !_context.IsGpuThread()) - { - return; - } - // There is a small gap here where the action is removed but _actionRegistered is still 1. // In this case it will skip registering the action, but here we are already handling it, // so there shouldn't be any issue as it's the same handler for all actions. diff --git a/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs b/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs index cca02bb156..ce970fab7c 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs @@ -23,11 +23,6 @@ namespace Ryujinx.Graphics.Gpu.Memory private readonly IVirtualMemoryManagerTracked _cpuMemory; private int _referenceCount; - /// - /// Indicates whenever the memory manager supports 4KB pages. - /// - public bool Supports4KBPages => _cpuMemory.Supports4KBPages; - /// /// In-memory shader cache. /// diff --git a/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs b/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs index 06b8fd3454..e8c433269e 100644 --- a/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs +++ b/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs @@ -72,7 +72,8 @@ namespace Ryujinx.HLE.HOS AddressSpace addressSpace = null; - if (mode == MemoryManagerMode.HostMapped || mode == MemoryManagerMode.HostMappedUnsafe) + // We want to use host tracked mode if the host page size is > 4KB. + if ((mode == MemoryManagerMode.HostMapped || mode == MemoryManagerMode.HostMappedUnsafe) && MemoryBlock.GetPageSize() <= 0x1000) { if (!AddressSpace.TryCreate(context.Memory, addressSpaceSize, MemoryBlock.GetPageSize() == MemoryManagerHostMapped.PageSize, out addressSpace)) { @@ -91,13 +92,21 @@ namespace Ryujinx.HLE.HOS case MemoryManagerMode.HostMapped: case MemoryManagerMode.HostMappedUnsafe: - if (addressSpaceSize != addressSpace.AddressSpaceSize) + if (addressSpace == null) { - Logger.Warning?.Print(LogClass.Emulation, $"Allocated address space (0x{addressSpace.AddressSpaceSize:X}) is smaller than guest application requirements (0x{addressSpaceSize:X})"); + var memoryManagerHostTracked = new MemoryManagerHostTracked(context.Memory, addressSpaceSize, mode == MemoryManagerMode.HostMappedUnsafe, invalidAccessHandler); + processContext = new ArmProcessContext(pid, cpuEngine, _gpu, memoryManagerHostTracked, addressSpaceSize, for64Bit); } + else + { + if (addressSpaceSize != addressSpace.AddressSpaceSize) + { + Logger.Warning?.Print(LogClass.Emulation, $"Allocated address space (0x{addressSpace.AddressSpaceSize:X}) is smaller than guest application requirements (0x{addressSpaceSize:X})"); + } - var memoryManagerHostMapped = new MemoryManagerHostMapped(addressSpace, mode == MemoryManagerMode.HostMappedUnsafe, invalidAccessHandler); - processContext = new ArmProcessContext(pid, cpuEngine, _gpu, memoryManagerHostMapped, addressSpace.AddressSpaceSize, for64Bit); + var memoryManagerHostMapped = new MemoryManagerHostMapped(addressSpace, mode == MemoryManagerMode.HostMappedUnsafe, invalidAccessHandler); + processContext = new ArmProcessContext(pid, cpuEngine, _gpu, memoryManagerHostMapped, addressSpace.AddressSpaceSize, for64Bit); + } break; default: diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs index 543acb7a0a..d7b601d1c5 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs @@ -165,6 +165,29 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory /// protected override Result MapForeign(IEnumerable regions, ulong va, ulong size) { + ulong backingStart = (ulong)Context.Memory.Pointer; + ulong backingEnd = backingStart + Context.Memory.Size; + + KPageList pageList = new(); + + foreach (HostMemoryRange region in regions) + { + // If the range is inside the physical memory, it is shared and we should increment the page count, + // otherwise it is private and we don't need to increment the page count. + + if (region.Address >= backingStart && region.Address < backingEnd) + { + pageList.AddRange(region.Address - backingStart + DramMemoryMap.DramBase, region.Size / PageSize); + } + } + + using var scopedPageList = new KScopedPageList(Context.MemoryManager, pageList); + + foreach (var pageNode in pageList) + { + Context.CommitMemory(pageNode.Address - DramMemoryMap.DramBase, pageNode.PagesCount * PageSize); + } + ulong offset = 0; foreach (var region in regions) @@ -174,6 +197,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory offset += region.Size; } + scopedPageList.SignalSuccess(); + return Result.Success; } diff --git a/src/Ryujinx.Memory/AddressSpaceManager.cs b/src/Ryujinx.Memory/AddressSpaceManager.cs index 0a4a951439..f19b45b659 100644 --- a/src/Ryujinx.Memory/AddressSpaceManager.cs +++ b/src/Ryujinx.Memory/AddressSpaceManager.cs @@ -283,9 +283,9 @@ namespace Ryujinx.Memory { var hostRegion = hostRegions[i]; - if ((ulong)hostRegion.Address >= backingStart && (ulong)hostRegion.Address < backingEnd) + if (hostRegion.Address >= backingStart && hostRegion.Address < backingEnd) { - regions[count++] = new MemoryRange((ulong)hostRegion.Address - backingStart, hostRegion.Size); + regions[count++] = new MemoryRange(hostRegion.Address - backingStart, hostRegion.Size); } } diff --git a/src/Ryujinx.Memory/Tracking/VirtualRegion.cs b/src/Ryujinx.Memory/Tracking/VirtualRegion.cs index bb087e9af0..35e9c2d9b2 100644 --- a/src/Ryujinx.Memory/Tracking/VirtualRegion.cs +++ b/src/Ryujinx.Memory/Tracking/VirtualRegion.cs @@ -70,9 +70,12 @@ namespace Ryujinx.Memory.Tracking { _lastPermission = MemoryPermission.Invalid; - foreach (RegionHandle handle in Handles) + if (!Guest) { - handle.SignalMappingChanged(mapped); + foreach (RegionHandle handle in Handles) + { + handle.SignalMappingChanged(mapped); + } } } From 7124d679fd4345f2ed517e77ab40d90e6bef9650 Mon Sep 17 00:00:00 2001 From: MutantAura <44103205+MutantAura@users.noreply.github.com> Date: Wed, 27 Mar 2024 17:55:34 +0000 Subject: [PATCH 20/87] UI: Friendly driver name reporting. (#6530) * Implement friendly VkDriverID names for UI. * Capitalise NVIDIA * Prefer vendor name on macOS * Typo fix Co-authored-by: gdkchan --------- Co-authored-by: gdkchan --- src/Ryujinx.Graphics.Vulkan/Vendor.cs | 32 +++++++++++++++++++ src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs | 7 ++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/Ryujinx.Graphics.Vulkan/Vendor.cs b/src/Ryujinx.Graphics.Vulkan/Vendor.cs index ff841dec93..e0f5690793 100644 --- a/src/Ryujinx.Graphics.Vulkan/Vendor.cs +++ b/src/Ryujinx.Graphics.Vulkan/Vendor.cs @@ -1,3 +1,4 @@ +using Silk.NET.Vulkan; using System.Text.RegularExpressions; namespace Ryujinx.Graphics.Vulkan @@ -61,5 +62,36 @@ namespace Ryujinx.Graphics.Vulkan _ => $"0x{id:X}", }; } + + public static string GetFriendlyDriverName(DriverId id) + { + return id switch + { + DriverId.AmdProprietary => "AMD", + DriverId.AmdOpenSource => "AMD (Open)", + DriverId.ArmProprietary => "ARM", + DriverId.BroadcomProprietary => "Broadcom", + DriverId.CoreaviProprietary => "CoreAVI", + DriverId.GgpProprietary => "GGP", + DriverId.GoogleSwiftshader => "SwiftShader", + DriverId.ImaginationProprietary => "Imagination", + DriverId.IntelOpenSourceMesa => "Intel (Open)", + DriverId.IntelProprietaryWindows => "Intel", + DriverId.JuiceProprietary => "Juice", + DriverId.MesaDozen => "Dozen", + DriverId.MesaLlvmpipe => "LLVMpipe", + DriverId.MesaPanvk => "PanVK", + DriverId.MesaRadv => "RADV", + DriverId.MesaTurnip => "Turnip", + DriverId.MesaV3DV => "V3DV", + DriverId.MesaVenus => "Venus", + DriverId.Moltenvk => "MoltenVK", + DriverId.NvidiaProprietary => "NVIDIA", + DriverId.QualcommProprietary => "Qualcomm", + DriverId.SamsungProprietary => "Samsung", + DriverId.VerisiliconProprietary => "Verisilicon", + _ => id.ToString(), + }; + } } } diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index ede54a6fc2..d1afeaeaed 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -313,8 +313,6 @@ namespace Ryujinx.Graphics.Vulkan var hasDriverProperties = _physicalDevice.TryGetPhysicalDeviceDriverPropertiesKHR(Api, out var driverProperties); - string vendorName = VendorUtils.GetNameFromId(properties.VendorID); - Vendor = VendorUtils.FromId(properties.VendorID); IsAmdWindows = Vendor == Vendor.Amd && OperatingSystem.IsWindows(); @@ -326,8 +324,9 @@ namespace Ryujinx.Graphics.Vulkan Vendor == Vendor.Broadcom || Vendor == Vendor.ImgTec; - GpuVendor = vendorName; - GpuDriver = hasDriverProperties ? Marshal.PtrToStringAnsi((IntPtr)driverProperties.DriverName) : vendorName; // Fall back to vendor name if driver name isn't available. + GpuVendor = VendorUtils.GetNameFromId(properties.VendorID); + GpuDriver = hasDriverProperties && !OperatingSystem.IsMacOS() ? + VendorUtils.GetFriendlyDriverName(driverProperties.DriverID) : GpuVendor; // Fallback to vendor name if driver is unavailable or on MacOS where vendor is preferred. fixed (byte* deviceName = properties.DeviceName) { From 6208c3e6f0b74873aae2ce49df8e89952ae61c5a Mon Sep 17 00:00:00 2001 From: Ac_K Date: Tue, 2 Apr 2024 18:21:14 +0200 Subject: [PATCH 21/87] New Crowdin updates (#6550) * New translations en_us.json (Spanish) * New translations en_us.json (Arabic) * New translations en_us.json (German) * New translations en_us.json (Greek) * New translations en_us.json (Italian) * New translations en_us.json (Korean) * New translations en_us.json (Polish) * New translations en_us.json (Russian) * New translations en_us.json (Turkish) * New translations en_us.json (Ukrainian) * New translations en_us.json (Chinese Traditional) * New translations en_us.json (Portuguese, Brazilian) * New translations en_us.json (Spanish) * New translations en_us.json (Arabic) * New translations en_us.json (German) * New translations en_us.json (Greek) * New translations en_us.json (Hebrew) * New translations en_us.json (Italian) * New translations en_us.json (Japanese) * New translations en_us.json (Korean) * New translations en_us.json (Polish) * New translations en_us.json (Russian) * New translations en_us.json (Turkish) * New translations en_us.json (French) * New translations en_us.json (Ukrainian) * New translations en_us.json (Chinese Simplified) * New translations en_us.json (Chinese Traditional) * New translations en_us.json (Portuguese, Brazilian) * New translations en_us.json (Thai) * New translations en_us.json (Italian) * New translations en_us.json (Japanese) * New translations en_us.json (Polish) * New translations en_us.json (Ukrainian) * New translations en_us.json (Chinese Simplified) * New translations en_us.json (Arabic) * New translations en_us.json (Italian) * New translations en_us.json (Russian) * New translations en_us.json (Chinese Simplified) * New translations en_us.json (Italian) * New translations en_us.json (German) * New translations en_us.json (Korean) * New translations en_us.json (Chinese Simplified) * New translations en_us.json (Arabic) * New translations en_us.json (Hebrew) * New translations en_us.json (Chinese Simplified) * New translations en_us.json (Arabic) --- src/Ryujinx/Assets/Locales/ar_SA.json | 109 ++++---- src/Ryujinx/Assets/Locales/de_DE.json | 37 +-- src/Ryujinx/Assets/Locales/el_GR.json | 9 +- src/Ryujinx/Assets/Locales/es_ES.json | 9 +- src/Ryujinx/Assets/Locales/fr_FR.json | 7 +- src/Ryujinx/Assets/Locales/he_IL.json | 9 +- src/Ryujinx/Assets/Locales/it_IT.json | 381 +++++++++++++------------- src/Ryujinx/Assets/Locales/ja_JP.json | 71 ++--- src/Ryujinx/Assets/Locales/ko_KR.json | 81 +++--- src/Ryujinx/Assets/Locales/pl_PL.json | 15 +- src/Ryujinx/Assets/Locales/pt_BR.json | 9 +- src/Ryujinx/Assets/Locales/ru_RU.json | 193 ++++++------- src/Ryujinx/Assets/Locales/th_TH.json | 7 +- src/Ryujinx/Assets/Locales/tr_TR.json | 9 +- src/Ryujinx/Assets/Locales/uk_UA.json | 67 ++--- src/Ryujinx/Assets/Locales/zh_CN.json | 125 +++++---- src/Ryujinx/Assets/Locales/zh_TW.json | 23 +- 17 files changed, 623 insertions(+), 538 deletions(-) diff --git a/src/Ryujinx/Assets/Locales/ar_SA.json b/src/Ryujinx/Assets/Locales/ar_SA.json index 57ca5b0aea..51df3a0522 100644 --- a/src/Ryujinx/Assets/Locales/ar_SA.json +++ b/src/Ryujinx/Assets/Locales/ar_SA.json @@ -1,5 +1,5 @@ { - "Language": "اَلْعَرَبِيَّةُ", + "Language": "العربية", "MenuBarFileOpenApplet": "فتح التطبيق المُصغَّر", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "افتح تطبيق محرر الـMii المُصغَّر في الوضع المستقل", "SettingsTabInputDirectMouseAccess": "الوصول المباشر للفأرة", @@ -11,8 +11,8 @@ "MenuBarFile": "_ملف", "MenuBarFileOpenFromFile": "_تحميل تطبيق من ملف", "MenuBarFileOpenUnpacked": "تحميل لعبه غير محزومه", - "MenuBarFileOpenEmuFolder": "فتح مجلّد Ryujinx", - "MenuBarFileOpenLogsFolder": "افتح مجلد السجلات", + "MenuBarFileOpenEmuFolder": "فتح مجلد Ryujinx", + "MenuBarFileOpenLogsFolder": "فتح مجلد السجلات", "MenuBarFileExit": "_خروج", "MenuBarOptions": "_خيارات", "MenuBarOptionsToggleFullscreen": "وضع ملء الشاشة", @@ -73,9 +73,9 @@ "GameListContextMenuCreateShortcut": "إنشاء اختصار للتطبيق", "GameListContextMenuCreateShortcutToolTip": "قم بإنشاء اختصار لسطح المكتب لتشغيل التطبيق المحدد", "GameListContextMenuCreateShortcutToolTipMacOS": "قم بإنشاء اختصار في مجلد تطبيقات نظام التشغيل MacOS الذي يقوم بتشغيل التطبيق المحدد", - "GameListContextMenuOpenModsDirectory": "افتح مجلد التعديلات", + "GameListContextMenuOpenModsDirectory": "فتح مجلد التعديلات", "GameListContextMenuOpenModsDirectoryToolTip": "يفتح المجلد الذي يحتوي على تعديلات التطبيق", - "GameListContextMenuOpenSdModsDirectory": "افتح مجلد تعديلات Atmosphere", + "GameListContextMenuOpenSdModsDirectory": "فتح مجلد تعديلات Atmosphere", "GameListContextMenuOpenSdModsDirectoryToolTip": "يفتح دليل Atmosphere لبطاقة SD البديلة الذي يحتوي على تعديلات التطبيق. مفيد للتعديلات التي تم تعبئتها للأجهزة الحقيقية.", "StatusBarGamesLoaded": "{0}/{1} الألعاب التي تم تحميلها", "StatusBarSystemVersion": "إصدار النظام: {0}", @@ -141,7 +141,7 @@ "SettingsTabSystemHacksNote": "قد يتسبب في عدم الاستقرار", "SettingsTabSystemExpandDramSize": "استخدام تخطيط الذاكرة البديل (المطورين)", "SettingsTabSystemIgnoreMissingServices": "تجاهل الخدمات المفقودة", - "SettingsTabGraphics": "الرسوميات", + "SettingsTabGraphics": "الرسومات", "SettingsTabGraphicsAPI": "الرسومات API", "SettingsTabGraphicsEnableShaderCache": "تفعيل ذاكرة المظللات المؤقتة", "SettingsTabGraphicsAnisotropicFiltering": "تصفية متباين الخواص:", @@ -174,8 +174,8 @@ "SettingsTabLoggingEnableErrorLogs": "تفعيل سجلات الأخطاء", "SettingsTabLoggingEnableTraceLogs": "تفعيل سجلات التتبع", "SettingsTabLoggingEnableGuestLogs": "تفعيل سجلات الضيوف", - "SettingsTabLoggingEnableFsAccessLogs": "Enable Fs Access Logs", - "SettingsTabLoggingFsGlobalAccessLogMode": "Fs Global Access Log Mode:", + "SettingsTabLoggingEnableFsAccessLogs": "تمكين سجلات الوصول لـ Fs", + "SettingsTabLoggingFsGlobalAccessLogMode": "وضع سجل وصول عالمي Fs :", "SettingsTabLoggingDeveloperOptions": "خيارات المطور", "SettingsTabLoggingDeveloperOptionsNote": "تحذير: سوف يقلل من الأداء", "SettingsTabLoggingGraphicsBackendLogLevel": "مستوى سجل خلفية الرسومات:", @@ -307,7 +307,7 @@ "DialogMessageSaveNotAvailableMessage": "لا يوجد حفظ لـ {0} [{1:x16}]", "DialogMessageSaveNotAvailableCreateSaveMessage": "هل ترغب في إنشاء حفظ لهذه اللعبة؟", "DialogConfirmationTitle": "Ryujinx - تأكيد", - "DialogUpdaterTitle": "Ryujinx - تحديث", + "DialogUpdaterTitle": "تحديث Ryujinx", "DialogErrorTitle": "Ryujinx - خطأ", "DialogWarningTitle": "Ryujinx - تحذير", "DialogExitTitle": "Ryujinx - الخروج", @@ -323,7 +323,7 @@ "DialogNcaExtractionCheckLogErrorMessage": "فشل الاستخراج. اقرأ ملف السجل لمزيد من المعلومات.", "DialogNcaExtractionSuccessMessage": "تم الاستخراج بنجاح.", "DialogUpdaterConvertFailedMessage": "فشل تحويل إصدار Ryujinx الحالي.", - "DialogUpdaterCancelUpdateMessage": "إلغاء التحديث!", + "DialogUpdaterCancelUpdateMessage": "إلغاء التحديث", "DialogUpdaterAlreadyOnLatestVersionMessage": "أنت تستخدم بالفعل أحدث إصدار من Ryujinx!", "DialogUpdaterFailedToGetVersionMessage": "حدث خطأ أثناء محاولة الحصول على معلومات الإصدار من إصدار GitHub. يمكن أن يحدث هذا إذا تم تجميع إصدار جديد بواسطة GitHub Actions. جرب مجددا بعد دقائق.", "DialogUpdaterConvertFailedGithubMessage": "فشل تحويل إصدار Ryujinx المستلم من إصدار Github.", @@ -355,7 +355,7 @@ "DialogErrorAppletErrorExceptionMessage": "Error displaying ErrorApplet Dialog: {0}", "DialogUserErrorDialogMessage": "{0}: {1}", "DialogUserErrorDialogInfoMessage": "لمزيد من المعلومات حول كيفية إصلاح هذا الخطأ، اتبع دليل الإعداد الخاص بنا.", - "DialogUserErrorDialogTitle": "Ryujinx Error ({0})", + "DialogUserErrorDialogTitle": "خطأ ريوجينكس ({0})", "DialogAmiiboApiTitle": "أميبو API", "DialogAmiiboApiFailFetchMessage": "حدث خطأ أثناء جلب المعلومات من واجهة برمجة التطبيقات.", "DialogAmiiboApiConnectErrorMessage": "غير قادر على الاتصال بخادم API Amiibo. قد تكون الخدمة متوقفة أو قد تحتاج إلى التحقق من اتصال الإنترنت الخاص بك على الإنترنت.", @@ -384,7 +384,7 @@ "DialogUserProfileUnsavedChangesSubMessage": "هل تريد تجاهل التغييرات؟", "DialogControllerSettingsModifiedConfirmMessage": "تم تحديث إعدادات وحدة التحكم الحالية.", "DialogControllerSettingsModifiedConfirmSubMessage": "هل تريد الحفظ ؟", - "DialogLoadFileErrorMessage": "{0}. Errored File: {1}", + "DialogLoadFileErrorMessage": "{0}. ملف خاطئ: {1}", "DialogModAlreadyExistsMessage": "التعديل موجود بالفعل", "DialogModInvalidMessage": "المجلد المحدد لا يحتوي على تعديل!", "DialogModDeleteNoParentMessage": "فشل الحذف: لم يمكن العثور على المجلد الرئيسي للتعديل\"{0}\"!", @@ -398,8 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "الملف المحدد لا يحتوي على تحديث للملف المحدد!", "DialogSettingsBackendThreadingWarningTitle": "Warning - Backend Threading", "DialogSettingsBackendThreadingWarningMessage": "Ryujinx must be restarted after changing this option for it to apply fully. Depending on your platform, you may need to manually disable your driver's own multithreading when using Ryujinx's.", - "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", - "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionWarningMessage": "أنت على وشك حذف التعديل: {0}\n\nهل انت متأكد انك تريد المتابعة؟", + "DialogModManagerDeletionAllWarningMessage": "أنت على وشك حذف كافة التعديلات لهذا العنوان.\n\nهل انت متأكد انك تريد المتابعة؟", "SettingsTabGraphicsFeaturesOptions": "المميزات", "SettingsTabGraphicsBackendMultithreading": "Graphics Backend Multithreading:", "CommonAuto": "تلقائي", @@ -411,19 +411,19 @@ "MenuBarOptionsPauseEmulation": "إيقاف مؤقت", "MenuBarOptionsResumeEmulation": "استئناف", "AboutUrlTooltipMessage": "انقر لفتح موقع Ryujinx في متصفحك الافتراضي.", - "AboutDisclaimerMessage": "Ryujinx is not affiliated with Nintendo™,\nor any of its partners, in any way.", - "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) is used\nin our Amiibo emulation.", - "AboutPatreonUrlTooltipMessage": "Click to open the Ryujinx Patreon page in your default browser.", - "AboutGithubUrlTooltipMessage": "Click to open the Ryujinx GitHub page in your default browser.", - "AboutDiscordUrlTooltipMessage": "Click to open an invite to the Ryujinx Discord server in your default browser.", - "AboutTwitterUrlTooltipMessage": "Click to open the Ryujinx Twitter page in your default browser.", + "AboutDisclaimerMessage": "ريوجينكس لا ينتمي إلى نينتندو™،\nأو أي من شركائها بأي شكل من الأشكال.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) يتم \nاستخدامه في محاكاة أمبيو لدينا.", + "AboutPatreonUrlTooltipMessage": "انقر لفتح صفحة ريوجينكس في باتريون في متصفحك الافتراضي.", + "AboutGithubUrlTooltipMessage": "انقر لفتح صفحة ريوجينكس في غيت هاب في متصفحك الافتراضي.", + "AboutDiscordUrlTooltipMessage": "انقر لفتح دعوة إلى خادم ريوجينكس في ديكسورد في متصفحك الافتراضي.", + "AboutTwitterUrlTooltipMessage": "انقر لفتح صفحة ريوجينكس في تويتر في متصفحك الافتراضي.", "AboutRyujinxAboutTitle": "حول:", - "AboutRyujinxAboutContent": "Ryujinx is an emulator for the Nintendo Switch™.\nPlease support us on Patreon.\nGet all the latest news on our Twitter or Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.", + "AboutRyujinxAboutContent": "ريوجينكس هو محاكي لجهاز نينتندو سويتش™.\nمن فضلك ادعمنا على باتريون.\nاحصل على آخر الأخبار على تويتر أو ديسكورد.\nيمكن للمطورين المهتمين بالمساهمة معرفة المزيد على غيت هاب أو ديسكورد.", "AboutRyujinxMaintainersTitle": "تم إصلاحها بواسطة:", "AboutRyujinxMaintainersContentTooltipMessage": "انقر لفتح صفحة المساهمين في متصفحك الافتراضي.", "AboutRyujinxSupprtersTitle": "مدعوم على باتريون بواسطة:", "AmiiboSeriesLabel": "مجموعة أميبو", - "AmiiboCharacterLabel": "Character", + "AmiiboCharacterLabel": "شخصية", "AmiiboScanButtonLabel": "فحصه", "AmiiboOptionsShowAllLabel": "إظهار كل أميبو", "AmiiboOptionsUsRandomTagLabel": "Hack: Use Random tag Uuid", @@ -444,38 +444,38 @@ "OrderDescending": "ترتيب تنازلي", "SettingsTabGraphicsFeatures": "الميزات والتحسينات", "ErrorWindowTitle": "نافذة الخطأ", - "ToggleDiscordTooltip": "Choose whether or not to display Ryujinx on your \"currently playing\" Discord activity", + "ToggleDiscordTooltip": "اختر ما إذا كنت تريد عرض ريوجينكس في نشاط ديسكورد \"يتم تشغيله حاليًا\" أم لا", "AddGameDirBoxTooltip": "أدخل دليل اللعبة لإضافته إلى القائمة", "AddGameDirTooltip": "إضافة دليل اللعبة إلى القائمة", "RemoveGameDirTooltip": "إزالة دليل اللعبة المحدد", - "CustomThemeCheckTooltip": "Use a custom Avalonia theme for the GUI to change the appearance of the emulator menus", + "CustomThemeCheckTooltip": "استخدم سمة أفالونيا المخصصة لواجهة المستخدم الرسومية لتغيير مظهر قوائم المحاكي", "CustomThemePathTooltip": "مسار سمة واجهة المستخدم المخصصة", "CustomThemeBrowseTooltip": "تصفح للحصول على سمة واجهة المستخدم المخصصة", - "DockModeToggleTooltip": "Docked mode makes the emulated system behave as a docked Nintendo Switch. This improves graphical fidelity in most games. Conversely, disabling this will make the emulated system behave as a handheld Nintendo Switch, reducing graphics quality.\n\nConfigure player 1 controls if planning to use docked mode; configure handheld controls if planning to use handheld mode.\n\nLeave ON if unsure.", - "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", - "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", + "DockModeToggleTooltip": "يجعل وضع الإرساء النظام الذي تمت محاكاته بمثابة جهاز نينتندو سويتش الذي تم إرساءه. يؤدي هذا إلى تحسين الدقة الرسومية في معظم الألعاب. على العكس من ذلك، سيؤدي تعطيل هذا إلى جعل النظام الذي تمت محاكاته يعمل كجهاز نينتندو سويتش محمول، مما يقلل من جودة الرسومات.\n\nقم بتكوين عناصر تحكم اللاعب 1 إذا كنت تخطط لاستخدام وضع الإرساء؛ قم بتكوين عناصر التحكم المحمولة إذا كنت تخطط لاستخدام الوضع المحمول.\n\nاتركه مشغل إذا لم تكن متأكدًا.", + "DirectKeyboardTooltip": "دعم الوصول المباشر إلى لوحة المفاتيح (HID). يوفر وصول الألعاب إلى لوحة المفاتيح الخاصة بك كجهاز لإدخال النص.\n\nيعمل فقط مع الألعاب التي تدعم استخدام لوحة المفاتيح في الأصل على أجهزة سويتش.\n\nاتركه معطلا إذا كنت غير متأكد.", + "DirectMouseTooltip": "دعم الوصول المباشر إلى لوحة المفاتيح (HID). يوفر وصول الألعاب إلى لوحة المفاتيح الخاصة بك كجهاز لإدخال النص.\n\nيعمل فقط مع الألعاب التي تدعم استخدام لوحة المفاتيح في الأصل على أجهزة سويتش.\n\nاتركه معطلا إذا كنت غير متأكد.", "RegionTooltip": "تغيير منطقة النظام", "LanguageTooltip": "تغيير لغة النظام", "TimezoneTooltip": "تغيير المنطقة الزمنية للنظام", "TimeTooltip": "تغيير وقت النظام", "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", "PptcToggleTooltip": "Saves translated JIT functions so that they do not need to be translated every time the game loads.\n\nReduces stuttering and significantly speeds up boot times after the first boot of a game.\n\nLeave ON if unsure.", - "FsIntegrityToggleTooltip": "Checks for corrupt files when booting a game, and if corrupt files are detected, displays a hash error in the log.\n\nHas no impact on performance and is meant to help troubleshooting.\n\nLeave ON if unsure.", + "FsIntegrityToggleTooltip": "يتحقق من وجود ملفات تالفة عند تشغيل لعبة ما، وإذا تم اكتشاف ملفات تالفة، فسيتم عرض خطأ تجزئة في السجل.\n\nليس له أي تأثير على الأداء ويهدف إلى المساعدة في استكشاف الأخطاء وإصلاحها.\n\nاتركه مفعلا إذا كنت غير متأكد.", "AudioBackendTooltip": "Changes the backend used to render audio.\n\nSDL2 is the preferred one, while OpenAL and SoundIO are used as fallbacks. Dummy will have no sound.\n\nSet to SDL2 if unsure.", "MemoryManagerTooltip": "Change how guest memory is mapped and accessed. Greatly affects emulated CPU performance.\n\nSet to HOST UNCHECKED if unsure.", - "MemoryManagerSoftwareTooltip": "Use a software page table for address translation. Highest accuracy but slowest performance.", + "MemoryManagerSoftwareTooltip": "استخدام جدول الصفحات البرمجي لترجمة العناوين. أعلى دقة ولكن أبطأ أداء.", "MemoryManagerHostTooltip": "Directly map memory in the host address space. Much faster JIT compilation and execution.", "MemoryManagerUnsafeTooltip": "Directly map memory, but do not mask the address within the guest address space before access. Faster, but at the cost of safety. The guest application can access memory from anywhere in Ryujinx, so only run programs you trust with this mode.", "UseHypervisorTooltip": "Use Hypervisor instead of JIT. Greatly improves performance when available, but can be unstable in its current state.", "DRamTooltip": "Utilizes an alternative MemoryMode layout to mimic a Switch development model.\n\nThis is only useful for higher-resolution texture packs or 4k resolution mods. Does NOT improve performance.\n\nLeave OFF if unsure.", - "IgnoreMissingServicesTooltip": "Ignores unimplemented Horizon OS services. This may help in bypassing crashes when booting certain games.\n\nLeave OFF if unsure.", + "IgnoreMissingServicesTooltip": "يتجاهل خدمات نظام هوريزون غير المنفذة. قد يساعد هذا في تجاوز الأعطال عند تشغيل ألعاب معينة.\n\nاتركه معطلا إذا كنت غير متأكد.", "GraphicsBackendThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.", "GalThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.", "ShaderCacheToggleTooltip": "Saves a disk shader cache which reduces stuttering in subsequent runs.\n\nLeave ON if unsure.", - "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", - "ResolutionScaleEntryTooltip": "Floating point resolution scale, such as 1.5. Non-integral scales are more likely to cause issues or crash.", - "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", - "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", + "ResolutionScaleTooltip": "يضاعف دقة عرض اللعبة.\n\nقد لا تعمل بعض الألعاب مع هذا وتبدو منقطة حتى عند زيادة الدقة؛ بالنسبة لهذه الألعاب، قد تحتاج إلى العثور على تعديلات تزيل الصقل أو تزيد من دقة العرض الداخلي. لاستخدام الأخير، من المحتمل أن ترغب في تحديد أصلي.\n\nيمكن تغيير هذا الخيار أثناء تشغيل اللعبة بالنقر فوق \"تطبيق\" أدناه؛ يمكنك ببساطة تحريك نافذة الإعدادات جانبًا والتجربة حتى تجد المظهر المفضل للعبة.\n\nضع في اعتبارك أن 4x مبالغة في أي إعداد تقريبًا.", + "ResolutionScaleEntryTooltip": "مقياس دقة النقطة العائمة، مثل 1.5. من المرجح أن تتسبب المقاييس غير المتكاملة في حدوث مشكلات أو تعطل.", + "AnisotropyTooltip": "مستوى تصفية متباين الخواص. اضبط على تلقائي لاستخدام القيمة التي تطلبها اللعبة.", + "AspectRatioTooltip": "يتم تطبيق نسبة العرض إلى الارتفاع على نافذة العارض.\n\nقم بتغيير هذا فقط إذا كنت تستخدم تعديل نسبة العرض إلى الارتفاع للعبتك، وإلا سيتم تمديد الرسومات.\n\nاتركه على 16:9 إذا لم تكن متأكدًا.", "ShaderDumpPathTooltip": "Graphics Shaders Dump Path", "FileLogTooltip": "حفظ تسجيل وحدة التحكم إلى ملف سجل على القرص. لا يؤثر على الأداء.", "StubLogTooltip": "Prints stub log messages in the console. Does not affect performance.", @@ -488,14 +488,14 @@ "FSAccessLogModeTooltip": "Enables FS access log output to the console. Possible modes are 0-3", "DeveloperOptionTooltip": "استخدمه بعناية", "OpenGlLogLevel": "يتطلب تمكين مستويات السجل المناسبة", - "DebugLogTooltip": "Prints debug log messages in the console.\n\nOnly use this if specifically instructed by a staff member, as it will make logs difficult to read and worsen emulator performance.", + "DebugLogTooltip": "طباعة رسائل سجل التصحيح في وحدة التحكم.\n\nاستخدم هذا فقط إذا طلب منك أحد الموظفين تحديدًا ذلك، لأنه سيجعل من الصعب قراءة السجلات وسيؤدي إلى تدهور أداء المحاكي.", "LoadApplicationFileTooltip": "افتح مستكشف الملفات لاختيار ملف متوافق مع Switch لتحميله", "LoadApplicationFolderTooltip": "افتح مستكشف الملفات لاختيار تطبيق متوافق مع Switch للتحميل", "OpenRyujinxFolderTooltip": "فتح مجلد نظام ملفات Ryujinx", - "OpenRyujinxLogsTooltip": "Opens the folder where logs are written to", + "OpenRyujinxLogsTooltip": "يفتح المجلد الذي تتم كتابة السجلات إليه", "ExitTooltip": "الخروج من Ryujinx", "OpenSettingsTooltip": "فتح نافذة الإعدادات", - "OpenProfileManagerTooltip": "افتح نافذة إدارة ملفات تعريف المستخدمين", + "OpenProfileManagerTooltip": "فتح نافذة إدارة ملفات تعريف المستخدمين", "StopEmulationTooltip": "إيقاف محاكاة اللعبة الحالية والعودة إلى اختيار اللعبة", "CheckUpdatesTooltip": "التحقق من وجود تحديثات لـ Ryujinx", "OpenAboutTooltip": "فتح حول النافذة", @@ -505,7 +505,7 @@ "AboutRyujinxContributorsButtonHeader": "رؤية جميع المساهمين", "SettingsTabSystemAudioVolume": "الحجم:", "AudioVolumeTooltip": "تغيير مستوى الصوت", - "SettingsTabSystemEnableInternetAccess": "Guest Internet Access/LAN Mode", + "SettingsTabSystemEnableInternetAccess": "الوصول إلى إنترنت كضيف/وضع LAN", "EnableInternetAccessTooltip": "للسماح للتطبيق المحاكي بالاتصال بالإنترنت.\n\nالألعاب ذات وضع الشبكة المحلية يمكن الاتصال ببعضها البعض عندما يتم تمكين هذا النظام وتكون الأنظمة متصلة بنفس نقطة الوصول. هذا يشمل وحدات التحكم الحقيقية أيضًا.\n\nلا يسمح بالاتصال بخوادم Nintendo. قد يسبب الانهيار في بعض الألعاب التي تحاول الاتصال بالإنترنت.\n\nاتركه متوقفا إن لم يكن مؤكدا.", "GameListContextMenuManageCheatToolTip": "إدارة الغش", "GameListContextMenuManageCheat": "إدارة الغش", @@ -533,10 +533,10 @@ "UserErrorApplicationNotFound": "التطبيق غير موجود", "UserErrorUnknown": "خطأ غير معروف", "UserErrorUndefined": "خطأ غير محدد", - "UserErrorNoKeysDescription": "Ryujinx was unable to find your 'prod.keys' file", - "UserErrorNoFirmwareDescription": "Ryujinx was unable to find any firmwares installed", - "UserErrorFirmwareParsingFailedDescription": "Ryujinx was unable to parse the provided firmware. This is usually caused by outdated keys.", - "UserErrorApplicationNotFoundDescription": "Ryujinx couldn't find a valid application at the given path.", + "UserErrorNoKeysDescription": "لم يتمكن Ryujinx من العثور على ملف 'prod.keys' الخاص بك", + "UserErrorNoFirmwareDescription": "لم يتمكن ريوجينكس من العثور على أية برامج ثابتة مثبتة", + "UserErrorFirmwareParsingFailedDescription": "لم يتمكن ريوجينكس من تحليل البرامج الثابتة المتوفرة. يحدث هذا عادة بسبب المفاتيح القديمة.", + "UserErrorApplicationNotFoundDescription": "تعذر على ريوجينكس العثور على تطبيق صالح في المسار المحدد.", "UserErrorUnknownDescription": "حدث خطأ غير معروف!", "UserErrorUndefinedDescription": "حدث خطأ غير محدد! لا ينبغي أن يحدث هذا، يرجى الاتصال بمطور!", "OpenSetupGuideMessage": "فتح دليل الإعداد", @@ -546,12 +546,12 @@ "RyujinxConfirm": "Ryujinx - تأكيد", "FileDialogAllTypes": "كل الأنواع", "Never": "مطلقاً", - "SwkbdMinCharacters": "Must be at least {0} characters long", - "SwkbdMinRangeCharacters": "Must be {0}-{1} characters long", + "SwkbdMinCharacters": "يجب أن يبلغ طوله {0} حرفًا على الأقل", + "SwkbdMinRangeCharacters": "يجب أن يتكون من {0}-{1} حرفًا", "SoftwareKeyboard": "لوحة المفاتيح البرمجية", "SoftwareKeyboardModeNumeric": "يجب أن يكون 0-9 أو '.' فقط", - "SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only", - "SoftwareKeyboardModeASCII": "Must be ASCII text only", + "SoftwareKeyboardModeAlphabet": "يجب أن تكون الأحرف غير CJK فقط", + "SoftwareKeyboardModeASCII": "يجب أن يكون نص ASCII فقط", "ControllerAppletControllers": "ذراع التحكم المدعومة:", "ControllerAppletPlayers": "اللاعبين:", "ControllerAppletDescription": "الإعدادات الحالية غير صالحة. افتح الإعدادات وأعد تكوين المدخلات الخاصة بك.", @@ -593,7 +593,7 @@ "Writable": "قابل للكتابة", "SelectDlcDialogTitle": "حدد ملفات DLC", "SelectUpdateDialogTitle": "حدد ملفات التحديث", - "SelectModDialogTitle": "Select mod directory", + "SelectModDialogTitle": "حدد مجلد التعديل", "UserProfileWindowTitle": "مدير ملفات تعريف المستخدمين", "CheatWindowTitle": "مدير الغش", "DlcWindowTitle": "إدارة المحتوى القابل للتنزيل لـ {0} ({1})", @@ -612,7 +612,7 @@ "UserProfileNoImageError": "يجب تعيين صورة الملف الشخصي", "GameUpdateWindowHeading": "إدارة التحديثات لـ {0} ({1})", "SettingsTabHotkeysResScaleUpHotkey": "زيادة الدقة:", - "SettingsTabHotkeysResScaleDownHotkey": "تقليل الدقة:", + "SettingsTabHotkeysResScaleDownHotkey": "خفض الدقة:", "UserProfilesName": "الاسم:", "UserProfilesUserId": "معرف المستخدم:", "SettingsTabGraphicsBackend": "خلفية الرسومات", @@ -626,7 +626,7 @@ "SettingsGpuBackendRestartSubMessage": "\n\nهل تريد إعادة التشغيل الآن؟", "RyujinxUpdaterMessage": "هل تريد تحديث Ryujinx إلى أحدث إصدار؟", "SettingsTabHotkeysVolumeUpHotkey": "زيادة مستوى الصوت:", - "SettingsTabHotkeysVolumeDownHotkey": "تقليل الحجم:", + "SettingsTabHotkeysVolumeDownHotkey": "خفض مستوى الصوت:", "SettingsEnableMacroHLE": "Enable Macro HLE", "SettingsEnableMacroHLETooltip": "High-level emulation of GPU Macro code.\n\nImproves performance, but may cause graphical glitches in some games.\n\nLeave ON if unsure.", "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", @@ -639,7 +639,7 @@ "SaveManagerTitle": "مدير الحفظ", "Name": "الاسم", "Size": "الحجم", - "Search": "البحث", + "Search": "بحث", "UserProfilesRecoverLostAccounts": "استعادة الحسابات المفقودة", "Recover": "استعادة", "UserProfilesRecoverHeading": "تم العثور على الحفظ للحسابات التالية", @@ -648,8 +648,11 @@ "GraphicsAALabel": "تنعيم الحواف:", "GraphicsScalingFilterLabel": "فلتر التكبير:", "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "المستوى", - "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", + "GraphicsScalingFilterLevelTooltip": "اضبط مستوى وضوح FSR 1.0. الأعلى هو أكثر وضوحا.", "SmaaLow": "SMAA منخفض", "SmaaMedium": "SMAA متوسط", "SmaaHigh": "SMAA عالي", @@ -664,5 +667,7 @@ "AboutChangelogButtonTooltipMessage": "انقر لفتح سجل التغيير لهذا الإصدار في متصفحك الافتراضي.", "SettingsTabNetworkMultiplayer": "لعب جماعي", "MultiplayerMode": "النمط:", - "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.", + "MultiplayerModeDisabled": "معطل", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/de_DE.json b/src/Ryujinx/Assets/Locales/de_DE.json index c70660532e..71af4f3feb 100644 --- a/src/Ryujinx/Assets/Locales/de_DE.json +++ b/src/Ryujinx/Assets/Locales/de_DE.json @@ -73,10 +73,10 @@ "GameListContextMenuCreateShortcut": "Erstelle Anwendungsverknüpfung", "GameListContextMenuCreateShortcutToolTip": "Erstelle eine Desktop-Verknüpfung die die gewählte Anwendung startet", "GameListContextMenuCreateShortcutToolTipMacOS": "Erstellen Sie eine Verknüpfung im MacOS-Programme-Ordner, die die ausgewählte Anwendung startet", - "GameListContextMenuOpenModsDirectory": "Open Mods Directory", - "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", + "GameListContextMenuOpenModsDirectory": "Mod-Verzeichnis öffnen", + "GameListContextMenuOpenModsDirectoryToolTip": "Öffnet das Verzeichnis, welches Mods für die Spiele beinhaltet", "GameListContextMenuOpenSdModsDirectory": "Atmosphere-Mod-Verzeichnis öffnen", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Öffnet das alternative SD-Karten-Atmosphere-Verzeichnis, das die Mods der Anwendung enthält. Dieser Ordner ist nützlich für Mods, die für echte Hardware erstellt worden sind.", "StatusBarGamesLoaded": "{0}/{1} Spiele geladen", "StatusBarSystemVersion": "Systemversion: {0}", "LinuxVmMaxMapCountDialogTitle": "Niedriges Limit für Speicherzuordnungen erkannt", @@ -452,13 +452,13 @@ "CustomThemePathTooltip": "Gibt den Pfad zum Design für die Emulator-Benutzeroberfläche an", "CustomThemeBrowseTooltip": "Ermöglicht die Suche nach einem benutzerdefinierten Design für die Emulator-Benutzeroberfläche", "DockModeToggleTooltip": "Im gedockten Modus verhält sich das emulierte System wie eine Nintendo Switch im TV Modus. Dies verbessert die grafische Qualität der meisten Spiele. Umgekehrt führt die Deaktivierung dazu, dass sich das emulierte System wie eine Nintendo Switch im Handheld Modus verhält, was die Grafikqualität beeinträchtigt.\n\nKonfiguriere das Eingabegerät für Spieler 1, um im Docked Modus zu spielen; konfiguriere das Controllerprofil via der Handheld Option, wenn geplant wird den Handheld Modus zu nutzen.\n\nIm Zweifelsfall AN lassen.", - "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", - "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", + "DirectKeyboardTooltip": "Direkter Zugriff auf die Tastatur (HID). Bietet Spielen Zugriff auf Ihre Tastatur als Texteingabegerät.\n\nFunktioniert nur mit Spielen, die die Tastaturnutzung auf Switch-Hardware nativ unterstützen.\n\nAus lassen, wenn unsicher.", + "DirectMouseTooltip": "Unterstützt den direkten Mauszugriff (HID). Bietet Spielen Zugriff auf Ihre Maus als Zeigegerät.\n\nFunktioniert nur mit Spielen, die nativ die Steuerung mit der Maus auf Switch-Hardware unterstützen (nur sehr wenige).\n\nTouchscreen-Funktionalität ist möglicherweise eingeschränkt, wenn dies aktiviert ist.\n\n Aus lassen, wenn unsicher.", "RegionTooltip": "Ändert die Systemregion", "LanguageTooltip": "Ändert die Systemsprache", "TimezoneTooltip": "Ändert die Systemzeitzone", "TimeTooltip": "Ändert die Systemzeit", - "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", + "VSyncToggleTooltip": "Vertikale Synchronisierung der emulierten Konsole. Diese Option ist quasi ein Frame-Limiter für die meisten Spiele; die Deaktivierung kann dazu führen, dass Spiele mit höherer Geschwindigkeit laufen oder Ladebildschirme länger benötigen/hängen bleiben.\n\nKann beim Spielen mit einem frei wählbaren Hotkey ein- und ausgeschaltet werden (standardmäßig F1). \n\nIm Zweifelsfall AN lassen.", "PptcToggleTooltip": "Speichert übersetzte JIT-Funktionen, sodass jene nicht jedes Mal übersetzt werden müssen, wenn das Spiel geladen wird.\n\nVerringert Stottern und die Zeit beim zweiten und den darauffolgenden Startvorgängen eines Spiels erheblich.\n\nIm Zweifelsfall AN lassen.", "FsIntegrityToggleTooltip": "Prüft beim Startvorgang auf beschädigte Dateien und zeigt bei beschädigten Dateien einen Hash-Fehler (Hash Error) im Log an.\n\nDiese Einstellung hat keinen Einfluss auf die Leistung und hilft bei der Fehlersuche.\n\nIm Zweifelsfall AN lassen.", "AudioBackendTooltip": "Ändert das Backend, das zum Rendern von Audio verwendet wird.\n\nSDL2 ist das bevorzugte Audio-Backend, OpenAL und SoundIO sind als Alternativen vorhanden. Dummy wird keinen Audio-Output haben.\n\nIm Zweifelsfall SDL2 auswählen.", @@ -472,10 +472,10 @@ "GraphicsBackendThreadingTooltip": "Führt Grafik-Backend Befehle auf einem zweiten Thread aus.\n\nDies beschleunigt die Shader-Kompilierung, reduziert Stottern und verbessert die Leistung auf GPU-Treibern ohne eigene Multithreading-Unterstützung. Geringfügig bessere Leistung bei Treibern mit Multithreading.\n\nIm Zweifelsfall auf AUTO stellen.", "GalThreadingTooltip": "Führt Grafik-Backend Befehle auf einem zweiten Thread aus.\n\nDies Beschleunigt die Shader-Kompilierung, reduziert Stottern und verbessert die Leistung auf GPU-Treibern ohne eigene Multithreading-Unterstützung. Geringfügig bessere Leistung bei Treibern mit Multithreading.\n\nIm Zweifelsfall auf auf AUTO stellen.", "ShaderCacheToggleTooltip": "Speichert einen persistenten Shader Cache, der das Stottern bei nachfolgenden Durchläufen reduziert.\n\nIm Zweifelsfall AN lassen.", - "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", + "ResolutionScaleTooltip": "Multipliziert die Rendering-Auflösung des Spiels.\n\nEinige wenige Spiele funktionieren damit nicht und sehen auch bei höherer Auflösung pixelig aus; für diese Spiele müssen Sie möglicherweise Mods finden, die Anti-Aliasing entfernen oder die interne Rendering-Auflösung erhöhen. Für die Verwendung von Letzterem sollten Sie Native wählen.\n\nSie können diese Option ändern, während ein Spiel läuft, indem Sie unten auf \"Übernehmen\" klicken; Sie können das Einstellungsfenster einfach zur Seite schieben und experimentieren, bis Sie Ihr bevorzugtes Aussehen für ein Spiel gefunden haben.\n\nDenken Sie daran, dass 4x für praktisch jedes Setup Overkill ist.", "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": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", - "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", + "AnisotropyTooltip": "Stufe der Anisotropen Filterung. Auf Auto setzen, um den vom Spiel geforderten Wert zu verwenden.", + "AspectRatioTooltip": "Seitenverhältnis, das auf das Renderer-Fenster angewendet wird.\n\nÄndern Sie dies nur, wenn Sie einen Seitenverhältnis-Mod für Ihr Spiel verwenden, da sonst die Grafik gestreckt wird.\n\nLassen Sie es auf 16:9, wenn Sie unsicher sind.", "ShaderDumpPathTooltip": "Grafik-Shader-Dump-Pfad", "FileLogTooltip": "Speichert die Konsolenausgabe in einer Log-Datei auf der Festplatte. Hat keinen Einfluss auf die Leistung.", "StubLogTooltip": "Ausgabe von Stub-Logs in der Konsole. Hat keinen Einfluss auf die Leistung.", @@ -616,9 +616,9 @@ "UserProfilesName": "Name:", "UserProfilesUserId": "Benutzer-ID:", "SettingsTabGraphicsBackend": "Grafik-Backend:", - "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", + "SettingsTabGraphicsBackendTooltip": "Wählen Sie das Grafik-Backend, das im Emulator verwendet werden soll.\n\nVulkan ist insgesamt besser für alle modernen Grafikkarten geeignet, sofern deren Treiber auf dem neuesten Stand sind. Vulkan bietet auch eine schnellere Shader-Kompilierung (weniger Stottern) auf allen GPU-Anbietern.\n\nOpenGL kann auf alten Nvidia-GPUs, alten AMD-GPUs unter Linux oder auf GPUs mit geringerem VRAM bessere Ergebnisse erzielen, obwohl die Shader-Kompilierung stärker stottert.\n\nSetzen Sie auf Vulkan, wenn Sie unsicher sind. Stellen Sie OpenGL ein, wenn Ihr Grafikprozessor selbst mit den neuesten Grafiktreibern Vulkan nicht unterstützt.", "SettingsEnableTextureRecompression": "Textur-Rekompression", - "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", + "SettingsEnableTextureRecompressionTooltip": "Komprimiert ASTC-Texturen, um die VRAM-Nutzung zu reduzieren.\n\nZu den Spielen, die dieses Texturformat verwenden, gehören Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder und The Legend of Zelda: Tears of the Kingdom.\n\nGrafikkarten mit 4GiB VRAM oder weniger werden beim Ausführen dieser Spiele wahrscheinlich irgendwann abstürzen.\n\nAktivieren Sie diese Option nur, wenn Ihnen bei den oben genannten Spielen der VRAM ausgeht. Lassen Sie es aus, wenn Sie unsicher sind.", "SettingsTabGraphicsPreferredGpu": "Bevorzugte GPU:", "SettingsTabGraphicsPreferredGpuTooltip": "Wähle die Grafikkarte aus, die mit dem Vulkan Grafik-Backend verwendet werden soll.\n\nDies hat keinen Einfluss auf die GPU die OpenGL verwendet.\n\nIm Zweifelsfall die als \"dGPU\" gekennzeichnete GPU auswählen. Diese Einstellung unberührt lassen, wenn keine zur Auswahl steht.", "SettingsAppRequiredRestartMessage": "Ein Neustart von Ryujinx ist erforderlich", @@ -644,12 +644,15 @@ "Recover": "Wiederherstellen", "UserProfilesRecoverHeading": "Speicherstände wurden für die folgenden Konten gefunden", "UserProfilesRecoverEmptyList": "Keine Profile zum Wiederherstellen", - "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", + "GraphicsAATooltip": "Wendet Anti-Aliasing auf das Rendering des Spiels an.\n\nFXAA verwischt den größten Teil des Bildes, während SMAA versucht, gezackte Kanten zu finden und sie zu glätten.\n\nEs wird nicht empfohlen, diese Option in Verbindung mit dem FSR-Skalierungsfilter zu verwenden.\n\nDiese Option kann geändert werden, während ein Spiel läuft, indem Sie unten auf \"Anwenden\" klicken; Sie können das Einstellungsfenster einfach zur Seite schieben und experimentieren, bis Sie Ihr bevorzugtes Aussehen für ein Spiel gefunden haben.\n\nLassen Sie die Option auf NONE, wenn Sie unsicher sind.", "GraphicsAALabel": "Antialiasing:", "GraphicsScalingFilterLabel": "Skalierungsfilter:", - "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterTooltip": "Wählen Sie den Skalierungsfilter, der bei der Auflösungsskalierung angewendet werden soll.\n\nBilinear eignet sich gut für 3D-Spiele und ist eine sichere Standardoption.\n\nNearest wird für Pixel-Art-Spiele empfohlen.\n\nFSR 1.0 ist lediglich ein Schärfungsfilter und wird nicht für die Verwendung mit FXAA oder SMAA empfohlen.\n\nDiese Option kann geändert werden, während ein Spiel läuft, indem Sie unten auf \"Anwenden\" klicken; Sie können das Einstellungsfenster einfach zur Seite schieben und experimentieren, bis Sie Ihr bevorzugtes Aussehen für ein Spiel gefunden haben.\n\nBleiben Sie auf BILINEAR, wenn Sie unsicher sind.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nächstes", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "Stufe", - "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", + "GraphicsScalingFilterLevelTooltip": "FSR 1.0 Schärfelevel festlegen. Höher ist schärfer.", "SmaaLow": "SMAA Niedrig", "SmaaMedium": "SMAA Mittel", "SmaaHigh": "SMAA Hoch", @@ -657,12 +660,14 @@ "UserEditorTitle": "Nutzer bearbeiten", "UserEditorTitleCreate": "Nutzer erstellen", "SettingsTabNetworkInterface": "Netzwerkschnittstelle:", - "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", + "NetworkInterfaceTooltip": "Die für LAN/LDN-Funktionen verwendete Netzwerkschnittstelle.\n\nIn Verbindung mit einem VPN oder XLink Kai und einem Spiel mit LAN-Unterstützung kann eine Verbindung mit demselben Netzwerk über das Internet vorgetäuscht werden.\n\nIm Zweifelsfall auf DEFAULT belassen.", "NetworkInterfaceDefault": "Standard", "PackagingShaders": "Verpackt Shader", "AboutChangelogButton": "Changelog in GitHub öffnen", "AboutChangelogButtonTooltipMessage": "Klicke hier, um das Changelog für diese Version in Ihrem Standardbrowser zu öffnen.", "SettingsTabNetworkMultiplayer": "Mehrspieler", "MultiplayerMode": "Modus:", - "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." + "MultiplayerModeTooltip": "Ändert den LDN-Mehrspielermodus.\n\nLdnMitm ändert die lokale drahtlose/lokale Spielfunktionalität in Spielen so, dass sie wie ein LAN funktioniert und lokale, netzwerkgleiche Verbindungen mit anderen Ryujinx-Instanzen und gehackten Nintendo Switch-Konsolen ermöglicht, auf denen das ldn_mitm-Modul installiert ist.\n\nMultiplayer erfordert, dass alle Spieler die gleiche Spielversion verwenden (d.h. Super Smash Bros. Ultimate v13.0.1 kann sich nicht mit v13.0.0 verbinden).\n\nIm Zweifelsfall auf DISABLED lassen.", + "MultiplayerModeDisabled": "Deaktiviert", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/el_GR.json b/src/Ryujinx/Assets/Locales/el_GR.json index 5b7e3b97d6..ba5c7078c6 100644 --- a/src/Ryujinx/Assets/Locales/el_GR.json +++ b/src/Ryujinx/Assets/Locales/el_GR.json @@ -341,7 +341,7 @@ "DialogThemeRestartMessage": "Το θέμα έχει αποθηκευτεί. Απαιτείται επανεκκίνηση για την εφαρμογή του θέματος.", "DialogThemeRestartSubMessage": "Θέλετε να κάνετε επανεκκίνηση", "DialogFirmwareInstallEmbeddedMessage": "Θα θέλατε να εγκαταστήσετε το Firmware που είναι ενσωματωμένο σε αυτό το παιχνίδι; (Firmware {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "Δεν βρέθηκε εγκατεστημένο Firmware, αλλά το Ryujinx μπόρεσε να εγκαταστήσει το Firmware {0} από το παρεχόμενο παιχνίδι.\nΟ εξομοιωτής θα ξεκινήσει τώρα.", + "DialogFirmwareInstallEmbeddedSuccessMessage": "No installed firmware was found but Ryujinx was able to install firmware {0} from the provided game.\nThe emulator will now start.", "DialogFirmwareNoFirmwareInstalledMessage": "Δεν έχει εγκατασταθεί Firmware", "DialogFirmwareInstalledMessage": "Το Firmware {0} εγκαταστάθηκε", "DialogInstallFileTypesSuccessMessage": "Επιτυχής εγκατάσταση τύπων αρχείων!", @@ -648,6 +648,9 @@ "GraphicsAALabel": "Anti-Aliasing", "GraphicsScalingFilterLabel": "Φίλτρο Κλιμάκωσης:", "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "Επίπεδο", "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "Χαμηλό SMAA", @@ -664,5 +667,7 @@ "AboutChangelogButtonTooltipMessage": "Κάντε κλικ για να ανοίξετε το αρχείο αλλαγών για αυτήν την έκδοση στο προεπιλεγμένο πρόγραμμα περιήγησης σας.", "SettingsTabNetworkMultiplayer": "Πολλαπλοί παίκτες", "MultiplayerMode": "Λειτουργία:", - "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.", + "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/es_ES.json b/src/Ryujinx/Assets/Locales/es_ES.json index ff41c855f1..38499c046f 100644 --- a/src/Ryujinx/Assets/Locales/es_ES.json +++ b/src/Ryujinx/Assets/Locales/es_ES.json @@ -341,7 +341,7 @@ "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.", + "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 hay firmware instalado", "DialogFirmwareInstalledMessage": "Se instaló el firmware {0}", "DialogInstallFileTypesSuccessMessage": "¡Tipos de archivos instalados con éxito!", @@ -648,6 +648,9 @@ "GraphicsAALabel": "Suavizado de bordes:", "GraphicsScalingFilterLabel": "Filtro de escalado:", "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "Nivel", "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "SMAA Bajo", @@ -664,5 +667,7 @@ "AboutChangelogButtonTooltipMessage": "Haga clic para abrir el registro de cambios para esta versión en su navegador predeterminado.", "SettingsTabNetworkMultiplayer": "Multijugador", "MultiplayerMode": "Modo:", - "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.", + "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/fr_FR.json b/src/Ryujinx/Assets/Locales/fr_FR.json index b1501d848d..bb2864cf2c 100644 --- a/src/Ryujinx/Assets/Locales/fr_FR.json +++ b/src/Ryujinx/Assets/Locales/fr_FR.json @@ -648,6 +648,9 @@ "GraphicsAALabel": "Anticrénelage :", "GraphicsScalingFilterLabel": "Filtre de mise à l'échelle :", "GraphicsScalingFilterTooltip": "Choisissez le filtre de mise à l'échelle qui sera appliqué lors de l'utilisation de la mise à l'échelle de la résolution.\n\nLe filtre bilinéaire fonctionne bien pour les jeux en 3D et constitue une option par défaut sûre.\n\nLe filtre le plus proche est recommandé pour les jeux de pixel art.\n\nFSR 1.0 est simplement un filtre de netteté, non recommandé pour une utilisation avec FXAA ou SMAA.\n\nCette option peut être modifiée pendant qu'un jeu est en cours d'exécution en cliquant sur \"Appliquer\" ci-dessous ; vous pouvez simplement déplacer la fenêtre des paramètres de côté et expérimenter jusqu'à ce que vous trouviez l'aspect souhaité pour un jeu.\n\nLaissez sur BILINEAR si vous n'êtes pas sûr.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "Niveau ", "GraphicsScalingFilterLevelTooltip": "Définissez le niveau de netteté FSR 1.0. Plus élevé signifie plus net.", "SmaaLow": "SMAA Faible", @@ -664,5 +667,7 @@ "AboutChangelogButtonTooltipMessage": "Cliquez pour ouvrir le changelog de cette version dans votre navigateur par défaut.", "SettingsTabNetworkMultiplayer": "Multijoueur", "MultiplayerMode": "Mode :", - "MultiplayerModeTooltip": "Changer le mode multijoueur LDN.\n\nLdnMitm modifiera la fonctionnalité de jeu sans fil local/jeu local dans les jeux pour fonctionner comme s'il s'agissait d'un LAN, permettant des connexions locales sur le même réseau avec d'autres instances de Ryujinx et des consoles Nintendo Switch piratées ayant le module ldn_mitm installé.\n\nLe multijoueur nécessite que tous les joueurs soient sur la même version du jeu (par exemple, Super Smash Bros. Ultimate v13.0.1 ne peut pas se connecter à v13.0.0).\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr." + "MultiplayerModeTooltip": "Changer le mode multijoueur LDN.\n\nLdnMitm modifiera la fonctionnalité de jeu sans fil local/jeu local dans les jeux pour fonctionner comme s'il s'agissait d'un LAN, permettant des connexions locales sur le même réseau avec d'autres instances de Ryujinx et des consoles Nintendo Switch piratées ayant le module ldn_mitm installé.\n\nLe multijoueur nécessite que tous les joueurs soient sur la même version du jeu (par exemple, Super Smash Bros. Ultimate v13.0.1 ne peut pas se connecter à v13.0.0).\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr.", + "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/he_IL.json b/src/Ryujinx/Assets/Locales/he_IL.json index afa92f8d29..16d68b775c 100644 --- a/src/Ryujinx/Assets/Locales/he_IL.json +++ b/src/Ryujinx/Assets/Locales/he_IL.json @@ -155,7 +155,7 @@ "SettingsTabGraphicsResolutionScaleNative": "מקורי (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Not recommended)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (לא מומלץ)", "SettingsTabGraphicsAspectRatio": "יחס גובה-רוחב:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -648,6 +648,9 @@ "GraphicsAALabel": "החלקת-עקומות:", "GraphicsScalingFilterLabel": "מסנן מידת איכות:", "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "רמה", "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "SMAA נמוך", @@ -664,5 +667,7 @@ "AboutChangelogButtonTooltipMessage": "לחץ כדי לפתוח את יומן השינויים עבור גרסה זו בדפדפן ברירת המחדל שלך.", "SettingsTabNetworkMultiplayer": "רב משתתפים", "MultiplayerMode": "מצב:", - "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.", + "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/it_IT.json b/src/Ryujinx/Assets/Locales/it_IT.json index 7db45b0015..0e832ffb6c 100644 --- a/src/Ryujinx/Assets/Locales/it_IT.json +++ b/src/Ryujinx/Assets/Locales/it_IT.json @@ -1,6 +1,6 @@ { "Language": "Italiano", - "MenuBarFileOpenApplet": "Apri Applet", + "MenuBarFileOpenApplet": "Apri applet", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Apri l'applet Mii Editor in modalità Standalone", "SettingsTabInputDirectMouseAccess": "Accesso diretto mouse", "SettingsTabSystemMemoryManagerMode": "Modalità di gestione della memoria:", @@ -12,7 +12,7 @@ "MenuBarFileOpenFromFile": "_Carica applicazione da un file", "MenuBarFileOpenUnpacked": "Carica _gioco estratto", "MenuBarFileOpenEmuFolder": "Apri cartella di Ryujinx", - "MenuBarFileOpenLogsFolder": "Apri cartella dei Log", + "MenuBarFileOpenLogsFolder": "Apri cartella dei log", "MenuBarFileExit": "_Esci", "MenuBarOptions": "_Opzioni", "MenuBarOptionsToggleFullscreen": "Schermo intero", @@ -40,29 +40,29 @@ "GameListHeaderDeveloper": "Sviluppatore", "GameListHeaderVersion": "Versione", "GameListHeaderTimePlayed": "Tempo di gioco", - "GameListHeaderLastPlayed": "Giocato l'ultima volta", + "GameListHeaderLastPlayed": "Ultima partita", "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", - "GameListContextMenuOpenDeviceSaveDirectory": "Apri la cartella dispositivo dell'utente", - "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Apre la cartella che contiene i salvataggi di gioco del dispositivo", - "GameListContextMenuOpenBcatSaveDirectory": "Apri la cartella BCAT dell'utente", - "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Apre la cartella che contiene i salvataggi BCAT dell'applicazione", + "GameListContextMenuOpenUserSaveDirectory": "Apri la cartella dei salvataggi dell'utente", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Apre la cartella che contiene i dati di salvataggio dell'utente", + "GameListContextMenuOpenDeviceSaveDirectory": "Apri la cartella dei salvataggi del dispositivo", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Apre la cartella che contiene i dati di salvataggio del dispositivo", + "GameListContextMenuOpenBcatSaveDirectory": "Apri la cartella del salvataggio BCAT", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Apre la cartella che contiene il salvataggio 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", + "GameListContextMenuManageDlc": "Gestisci DLC", + "GameListContextMenuManageDlcToolTip": "Apre la finestra di gestione dei DLC", "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", + "GameListContextMenuCacheManagementPurgePptc": "Accoda rigenerazione della cache PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Esegue la rigenerazione della cache PPTC al prossimo avvio del gioco", + "GameListContextMenuCacheManagementPurgeShaderCache": "Elimina la cache degli shader", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Elimina la cache degli shader dell'applicazione", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Apri la cartella della cache PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Apre la cartella che contiene la cache PPTC dell'applicazione", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Apri la cartella della cache degli shader", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Apre la cartella che contiene la cache degli shader dell'applicazione", "GameListContextMenuExtractData": "Estrai dati", "GameListContextMenuExtractDataExeFS": "ExeFS", "GameListContextMenuExtractDataExeFSToolTip": "Estrae la sezione ExeFS dall'attuale configurazione dell'applicazione (includendo aggiornamenti)", @@ -70,16 +70,16 @@ "GameListContextMenuExtractDataRomFSToolTip": "Estrae la sezione RomFS dall'attuale configurazione dell'applicazione (includendo aggiornamenti)", "GameListContextMenuExtractDataLogo": "Logo", "GameListContextMenuExtractDataLogoToolTip": "Estrae la sezione Logo dall'attuale configurazione dell'applicazione (includendo aggiornamenti)", - "GameListContextMenuCreateShortcut": "Crea Collegamento", - "GameListContextMenuCreateShortcutToolTip": "Crea un collegamento del gioco sul Desktop", - "GameListContextMenuCreateShortcutToolTipMacOS": "Crea un collegamento del gioco sulla Scrivania", - "GameListContextMenuOpenModsDirectory": "Apri la cartella delle Mods", - "GameListContextMenuOpenModsDirectoryToolTip": "Apre la cartella delle Mods del gioco", - "GameListContextMenuOpenSdModsDirectory": "Apri la cartella delle Mods Atmosphere", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Apre la cartella delle Mods Atmosphere (Utilizzare solo per le Mods eseguite su Hardware reale)", - "StatusBarGamesLoaded": "{0}/{1} Giochi caricati", + "GameListContextMenuCreateShortcut": "Crea collegamento", + "GameListContextMenuCreateShortcutToolTip": "Crea un collegamento sul desktop che avvia l'applicazione selezionata", + "GameListContextMenuCreateShortcutToolTipMacOS": "Crea un collegamento nella cartella Applicazioni di macOS che avvia l'applicazione selezionata", + "GameListContextMenuOpenModsDirectory": "Apri la cartella delle mod", + "GameListContextMenuOpenModsDirectoryToolTip": "Apre la cartella che contiene le mod dell'applicazione", + "GameListContextMenuOpenSdModsDirectory": "Apri la cartella delle mod Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Apre la cartella alternativa di Atmosphere sulla scheda SD che contiene le mod dell'applicazione. Utile per le mod create per funzionare sull'hardware reale.", + "StatusBarGamesLoaded": "{0}/{1} giochi caricati", "StatusBarSystemVersion": "Versione di sistema: {0}", - "LinuxVmMaxMapCountDialogTitle": "Limite basso per le mappature di memoria rilevate", + "LinuxVmMaxMapCountDialogTitle": "Rilevato limite basso per le mappature di memoria", "LinuxVmMaxMapCountDialogTextPrimary": "Vuoi aumentare il valore di vm.max_map_count a {0}?", "LinuxVmMaxMapCountDialogTextSecondary": "Alcuni giochi potrebbero provare a creare più mappature di memoria di quanto sia attualmente consentito. Ryujinx si bloccherà non appena questo limite viene superato.", "LinuxVmMaxMapCountDialogButtonUntilRestart": "Sì, fino al prossimo riavvio", @@ -94,7 +94,7 @@ "SettingsTabGeneralShowConfirmExitDialog": "Mostra dialogo \"Conferma Uscita\"", "SettingsTabGeneralHideCursor": "Nascondi il cursore:", "SettingsTabGeneralHideCursorNever": "Mai", - "SettingsTabGeneralHideCursorOnIdle": "Nascondi cursore inattivo", + "SettingsTabGeneralHideCursorOnIdle": "Quando è inattivo", "SettingsTabGeneralHideCursorAlways": "Sempre", "SettingsTabGeneralGameDirectories": "Cartelle dei giochi", "SettingsTabGeneralAdd": "Aggiungi", @@ -133,17 +133,17 @@ "SettingsTabSystemEnablePptc": "Attiva PPTC (Profiled Persistent Translation Cache)", "SettingsTabSystemEnableFsIntegrityChecks": "Attiva controlli d'integrità FS", "SettingsTabSystemAudioBackend": "Backend audio:", - "SettingsTabSystemAudioBackendDummy": "Manichino", + "SettingsTabSystemAudioBackendDummy": "Dummy", "SettingsTabSystemAudioBackendOpenAL": "OpenAL", - "SettingsTabSystemAudioBackendSoundIO": "AudioIO", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSDL2": "SDL2", - "SettingsTabSystemHacks": "Trucchi", - "SettingsTabSystemHacksNote": " (Possono causare instabilità)", - "SettingsTabSystemExpandDramSize": "Espandi dimensione DRAM a 6GiB", + "SettingsTabSystemHacks": "Espedienti", + "SettingsTabSystemHacksNote": "Possono causare instabilità", + "SettingsTabSystemExpandDramSize": "Usa layout di memoria alternativo (per sviluppatori)", "SettingsTabSystemIgnoreMissingServices": "Ignora servizi mancanti", "SettingsTabGraphics": "Grafica", - "SettingsTabGraphicsAPI": "API Grafiche", - "SettingsTabGraphicsEnableShaderCache": "Attiva Shader Cache", + "SettingsTabGraphicsAPI": "API grafica", + "SettingsTabGraphicsEnableShaderCache": "Attiva la cache degli shader", "SettingsTabGraphicsAnisotropicFiltering": "Filtro anisotropico:", "SettingsTabGraphicsAnisotropicFilteringAuto": "Auto", "SettingsTabGraphicsAnisotropicFiltering2x": "2x", @@ -163,34 +163,34 @@ "SettingsTabGraphicsAspectRatio21x9": "21:9", "SettingsTabGraphicsAspectRatio32x9": "32:9", "SettingsTabGraphicsAspectRatioStretch": "Adatta alla finestra", - "SettingsTabGraphicsDeveloperOptions": "Opzioni da sviluppatore", - "SettingsTabGraphicsShaderDumpPath": "Percorso di dump degli shaders:", + "SettingsTabGraphicsDeveloperOptions": "Opzioni per sviluppatori", + "SettingsTabGraphicsShaderDumpPath": "Percorso di dump degli shader:", "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à log accesso globale Fs:", - "SettingsTabLoggingDeveloperOptions": "Opzioni da sviluppatore (AVVISO: Ridurrà le prestazioni)", + "SettingsTabLoggingEnableStubLogs": "Attiva log di stub", + "SettingsTabLoggingEnableInfoLogs": "Attiva log di informazioni", + "SettingsTabLoggingEnableWarningLogs": "Attiva log di avviso", + "SettingsTabLoggingEnableErrorLogs": "Attiva log di errore", + "SettingsTabLoggingEnableTraceLogs": "Attiva log di trace", + "SettingsTabLoggingEnableGuestLogs": "Attiva log del guest", + "SettingsTabLoggingEnableFsAccessLogs": "Attiva log di accesso FS", + "SettingsTabLoggingFsGlobalAccessLogMode": "Modalità log di accesso globale FS:", + "SettingsTabLoggingDeveloperOptions": "Opzioni per sviluppatori", "SettingsTabLoggingDeveloperOptionsNote": "ATTENZIONE: ridurrà le prestazioni", - "SettingsTabLoggingGraphicsBackendLogLevel": "Livello registro backend grafico:", + "SettingsTabLoggingGraphicsBackendLogLevel": "Livello di log del backend grafico:", "SettingsTabLoggingGraphicsBackendLogLevelNone": "Nessuno", "SettingsTabLoggingGraphicsBackendLogLevelError": "Errore", "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Rallentamenti", "SettingsTabLoggingGraphicsBackendLogLevelAll": "Tutto", - "SettingsTabLoggingEnableDebugLogs": "Attiva logs di debug", + "SettingsTabLoggingEnableDebugLogs": "Attiva log di debug", "SettingsTabInput": "Input", "SettingsTabInputEnableDockedMode": "Attiva modalità TV", "SettingsTabInputDirectKeyboardAccess": "Accesso diretto alla tastiera", "SettingsButtonSave": "Salva", "SettingsButtonClose": "Chiudi", "SettingsButtonOk": "OK", - "SettingsButtonCancel": "Cancella", + "SettingsButtonCancel": "Annulla", "SettingsButtonApply": "Applica", "ControllerSettingsPlayer": "Giocatore", "ControllerSettingsPlayer1": "Giocatore 1", @@ -237,8 +237,8 @@ "ControllerSettingsStickInvertXAxis": "Inverti levetta X", "ControllerSettingsStickInvertYAxis": "Inverti levetta Y", "ControllerSettingsStickDeadzone": "Zona morta:", - "ControllerSettingsLStick": "Stick sinistro", - "ControllerSettingsRStick": "Stick destro", + "ControllerSettingsLStick": "Levetta sinistra", + "ControllerSettingsRStick": "Levetta destra", "ControllerSettingsTriggersLeft": "Grilletto sinistro", "ControllerSettingsTriggersRight": "Grilletto destro", "ControllerSettingsTriggersButtonsLeft": "Pulsante dorsale sinistro", @@ -252,7 +252,7 @@ "ControllerSettingsLeftSR": "SR", "ControllerSettingsRightSL": "SL", "ControllerSettingsRightSR": "SR", - "ControllerSettingsExtraButtonsLeft": "Tasto sinitro", + "ControllerSettingsExtraButtonsLeft": "Tasto sinistro", "ControllerSettingsExtraButtonsRight": "Tasto destro", "ControllerSettingsMisc": "Varie", "ControllerSettingsTriggerThreshold": "Sensibilità dei grilletti:", @@ -279,10 +279,10 @@ "ProfileImageSelectionNote": "Puoi importare un'immagine profilo personalizzata o selezionare un avatar dal firmware del sistema", "ProfileImageSelectionImportImage": "Importa file immagine", "ProfileImageSelectionSelectAvatar": "Seleziona avatar dal firmware", - "InputDialogTitle": "Finestra di dialogo ", + "InputDialogTitle": "Finestra di input", "InputDialogOk": "OK", "InputDialogCancel": "Annulla", - "InputDialogAddNewProfileTitle": "Scegli il nome profilo", + "InputDialogAddNewProfileTitle": "Scegli il nome del profilo", "InputDialogAddNewProfileHeader": "Digita un nome profilo", "InputDialogAddNewProfileSubtext": "(Lunghezza massima: {0})", "AvatarChoose": "Scegli", @@ -292,8 +292,8 @@ "ControllerSettingsAddProfileToolTip": "Aggiungi profilo", "ControllerSettingsRemoveProfileToolTip": "Rimuovi profilo", "ControllerSettingsSaveProfileToolTip": "Salva profilo", - "MenuBarFileToolsTakeScreenshot": "Fai uno screenshot", - "MenuBarFileToolsHideUi": "Nascondi UI", + "MenuBarFileToolsTakeScreenshot": "Cattura uno screenshot", + "MenuBarFileToolsHideUi": "Nascondi l'interfaccia", "GameListContextMenuRunApplication": "Esegui applicazione", "GameListContextMenuToggleFavorite": "Preferito", "GameListContextMenuToggleFavoriteToolTip": "Segna il gioco come preferito", @@ -318,14 +318,14 @@ "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", + "DialogNcaExtractionTitle": "Ryujinx - Estrazione 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.", + "DialogNcaExtractionCheckLogErrorMessage": "L'estrazione è fallita. Consulta il file di log per maggiori informazioni.", "DialogNcaExtractionSuccessMessage": "Estrazione completata con successo.", "DialogUpdaterConvertFailedMessage": "La conversione dell'attuale versione di Ryujinx è fallita.", - "DialogUpdaterCancelUpdateMessage": "Annullando l'aggiornamento!", + "DialogUpdaterCancelUpdateMessage": "Annullamento dell'aggiornamento in corso!", "DialogUpdaterAlreadyOnLatestVersionMessage": "Stai già usando la versione più recente di Ryujinx!", - "DialogUpdaterFailedToGetVersionMessage": "Si è verificato un errore durante il tentativo di ottenere informazioni sulla versione da GitHub Release. Ciò può essere causato se una nuova versione viene compilata da GitHub Actions. Riprova tra qualche minuto.", + "DialogUpdaterFailedToGetVersionMessage": "Si è verificato un errore durante il tentativo di recuperare le informazioni sulla versione da GitHub Release. Ciò può verificarsi se una nuova versione è in fase di compilazione da 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...", @@ -338,7 +338,7 @@ "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.", + "DialogThemeRestartMessage": "Il tema è stato salvato. È richiesto un riavvio per applicare il 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 ad installare il firmware {0} dal gioco fornito.\nL'emulatore si avvierà adesso.", @@ -354,7 +354,7 @@ "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.", + "DialogUserErrorDialogInfoMessage": "\nPer maggiori 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.", @@ -364,10 +364,10 @@ "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}", + "DialogPPTCDeletionMessage": "Stai per accodare la rigenerazione della cache PPTC al prossimo avvio per:\n\n{0}\n\nSei sicuro di voler proseguire?", + "DialogPPTCDeletionErrorMessage": "Errore nell'eliminazione della cache PPTC a {0}: {1}", + "DialogShaderDeletionMessage": "Stai per eliminare la cache degli shader per:\n\n{0}\n\nSei sicuro di voler proseguire?", + "DialogShaderDeletionErrorMessage": "Errore nell'eliminazione della cache degli shader a {0}: {1}", "DialogRyujinxErrorMessage": "Ryujinx ha incontrato un errore", "DialogInvalidTitleIdErrorMessage": "Errore UI: Il gioco selezionato non ha un ID titolo valido", "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Un firmware del sistema valido non è stato trovato in {0}.", @@ -385,30 +385,30 @@ "DialogControllerSettingsModifiedConfirmMessage": "Le attuali impostazioni del controller sono state aggiornate.", "DialogControllerSettingsModifiedConfirmSubMessage": "Vuoi salvare?", "DialogLoadFileErrorMessage": "{0}. Errore File: {1}", - "DialogModAlreadyExistsMessage": "La Mod risulta già installata", - "DialogModInvalidMessage": "Questa cartella non contiene nessuna Mods!", - "DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!", + "DialogModAlreadyExistsMessage": "La mod risulta già installata", + "DialogModInvalidMessage": "La cartella specificata non contiene nessuna mod!", + "DialogModDeleteNoParentMessage": "Eliminazione non riuscita: impossibile trovare la directory superiore per la mod \"{0}\"!", "DialogDlcNoDlcErrorMessage": "Il file specificato non contiene un DLC per il titolo selezionato!", "DialogPerformanceCheckLoggingEnabledMessage": "Hai abilitato il trace logging, che è progettato per essere usato solo dagli sviluppatori.", - "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Per prestazioni ottimali, si raccomanda di disabilitare il trace logging. Vuoi disabilitare il debug logging adesso?", - "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?", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Per prestazioni ottimali, si raccomanda di disabilitare il trace logging. Vuoi disabilitarlo adesso?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Hai abilitato il dump degli shader, che è progettato per essere usato solo dagli sviluppatori.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Per prestazioni ottimali, si raccomanda di disabilitare il dump degli shader. Vuoi disabilitarlo 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.", - "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", - "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionWarningMessage": "Stai per eliminare la mod: {0}\n\nConfermi di voler procedere?", + "DialogModManagerDeletionAllWarningMessage": "Stai per eliminare tutte le mod per questo titolo.\n\nVuoi davvero procedere?", "SettingsTabGraphicsFeaturesOptions": "Funzionalità", - "SettingsTabGraphicsBackendMultithreading": "Graphics Backend Multithreading", + "SettingsTabGraphicsBackendMultithreading": "Multithreading del backend grafico:", "CommonAuto": "Auto", - "CommonOff": "Spento", - "CommonOn": "Acceso", - "InputDialogYes": "Si", + "CommonOff": "Disattivato", + "CommonOn": "Attivo", + "InputDialogYes": "Sì", "InputDialogNo": "No", - "DialogProfileInvalidProfileNameErrorMessage": "Il nome del file contiene caratteri non validi. Riprova.", - "MenuBarOptionsPauseEmulation": "Pausa", + "DialogProfileInvalidProfileNameErrorMessage": "Il nome del file contiene dei caratteri non validi. Riprova.", + "MenuBarOptionsPauseEmulation": "Metti in 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.", @@ -418,15 +418,15 @@ "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.", + "AboutRyujinxAboutContent": "Ryujinx è un emulatore per la console Nintendo Switch™.\nSostienici 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.", + "AboutRyujinxMaintainersContentTooltipMessage": "Clicca per aprire la pagina dei contributori nel tuo browser predefinito.", "AboutRyujinxSupprtersTitle": "Supportato su Patreon da:", "AmiiboSeriesLabel": "Serie Amiibo", "AmiiboCharacterLabel": "Personaggio", - "AmiiboScanButtonLabel": "Scannerizza", + "AmiiboScanButtonLabel": "Scansiona", "AmiiboOptionsShowAllLabel": "Mostra tutti gli amiibo", - "AmiiboOptionsUsRandomTagLabel": "Hack: Usa un tag uuid casuale", + "AmiiboOptionsUsRandomTagLabel": "Espediente: Usa un UUID del tag casuale", "DlcManagerTableHeadingEnabledLabel": "Abilitato", "DlcManagerTableHeadingTitleIdLabel": "ID Titolo", "DlcManagerTableHeadingContainerPathLabel": "Percorso del contenitore", @@ -434,7 +434,7 @@ "DlcManagerRemoveAllButton": "Rimuovi tutti", "DlcManagerEnableAllButton": "Abilita tutto", "DlcManagerDisableAllButton": "Disabilita tutto", - "ModManagerDeleteAllButton": "Delete All", + "ModManagerDeleteAllButton": "Elimina tutto", "MenuBarOptionsChangeLanguage": "Cambia lingua", "MenuBarShowFileTypes": "Mostra tipi di file", "CommonSort": "Ordina", @@ -442,75 +442,75 @@ "CommonFavorite": "Preferito", "OrderAscending": "Crescente", "OrderDescending": "Decrescente", - "SettingsTabGraphicsFeatures": "Funzionalità & Miglioramenti", - "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", + "SettingsTabGraphicsFeatures": "Funzionalità e miglioramenti", + "ErrorWindowTitle": "Finestra di errore", + "ToggleDiscordTooltip": "Scegli se mostrare o meno Ryujinx nella tua attività su Discord", + "AddGameDirBoxTooltip": "Inserisci una cartella dei giochi per aggiungerla alla lista", + "AddGameDirTooltip": "Aggiungi una cartella dei giochi alla lista", + "RemoveGameDirTooltip": "Rimuovi la cartella dei giochi 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": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", - "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", + "DockModeToggleTooltip": "La modalità TV fa sì che il sistema emulato si comporti come una Nintendo Switch posizionata nella sua base. Ciò migliora la qualità grafica nella maggior parte dei giochi. Al contrario, disabilitandola il sistema emulato si comporterà come una Nintendo Switch in modalità portatile, riducendo la qualità grafica.\n\nConfigura i controlli del giocatore 1 se intendi usare la modalità TV; configura i controlli della modalità portatile se intendi usare quest'ultima.\n\nNel dubbio, lascia l'opzione attiva.", + "DirectKeyboardTooltip": "Supporto per l'accesso diretto alla tastiera (HID). Fornisce ai giochi l'accesso alla tastiera come dispositivo di inserimento del testo.\n\nFunziona solo con i giochi che supportano nativamente l'utilizzo della tastiera su hardware Switch.\n\nNel dubbio, lascia l'opzione disattivata.", + "DirectMouseTooltip": "Supporto per l'accesso diretto al mouse (HID). Fornisce ai giochi l'accesso al mouse come dispositivo di puntamento.\n\nFunziona solo con i rari giochi che supportano nativamente l'utilizzo del mouse su hardware Switch.\n\nQuando questa opzione è attivata, il touchscreen potrebbe non funzionare.\n\nNel dubbio, lascia l'opzione disattivata.", "RegionTooltip": "Cambia regione di sistema", "LanguageTooltip": "Cambia lingua di sistema", "TimezoneTooltip": "Cambia fuso orario di sistema", "TimeTooltip": "Cambia data e ora di sistema", - "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", - "PptcToggleTooltip": "Attiva o disattiva PPTC", - "FsIntegrityToggleTooltip": "Attiva controlli d'integrità sui file dei contenuti di gioco", - "AudioBackendTooltip": "Cambia backend audio", - "MemoryManagerTooltip": "Cambia il modo in cui la memoria guest è mappata e vi si accede. Influisce notevolmente sulle prestazioni della CPU emulata.", + "VSyncToggleTooltip": "Sincronizzazione verticale della console Emulata. Essenzialmente un limitatore di frame per la maggior parte dei giochi; disabilitarlo può far girare giochi a velocità più alta, allungare le schermate di caricamento o farle bloccare.\n\nPuò essere attivata in gioco con un tasto di scelta rapida (F1 per impostazione predefinita). Ti consigliamo di farlo se hai intenzione di disabilitarlo.\n\nLascia ON se non sei sicuro.", + "PptcToggleTooltip": "Salva le funzioni JIT tradotte in modo che non debbano essere tradotte tutte le volte che si avvia un determinato gioco.\n\nRiduce i fenomeni di stuttering e velocizza sensibilmente gli avvii successivi del gioco.\n\nNel dubbio, lascia l'opzione attiva.", + "FsIntegrityToggleTooltip": "Controlla la presenza di file corrotti quando si avvia un gioco. Se vengono rilevati dei file corrotti, verrà mostrato un errore di hash nel log.\n\nQuesta opzione non influisce sulle prestazioni ed è pensata per facilitare la risoluzione dei problemi.\n\nNel dubbio, lascia l'opzione attiva.", + "AudioBackendTooltip": "Cambia il backend usato per riprodurre l'audio.\n\nSDL2 è quello preferito, mentre OpenAL e SoundIO sono usati come ripiego. Dummy non riprodurrà alcun suono.\n\nNel dubbio, imposta l'opzione su SDL2.", + "MemoryManagerTooltip": "Cambia il modo in cui la memoria guest è mappata e vi si accede. Influisce notevolmente sulle prestazioni della CPU emulata.\n\nNel dubbio, imposta l'opzione su Host Unchecked.", "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à.", "UseHypervisorTooltip": "Usa Hypervisor invece di JIT. Migliora notevolmente le prestazioni quando disponibile, ma può essere instabile nel suo stato attuale.", - "DRamTooltip": "Espande l'ammontare di memoria sul sistema emulato da 4GiB A 6GiB", - "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": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", + "DRamTooltip": "Utilizza un layout di memoria alternativo per imitare un'unità di sviluppo di Switch.\n\nQuesta opzione è utile soltanto per i pacchetti di texture ad alta risoluzione o per le mod che aumentano la risoluzione a 4K. NON migliora le prestazioni.\n\nNel dubbio, lascia l'opzione disattivata.", + "IgnoreMissingServicesTooltip": "Ignora i servizi non implementati del sistema operativo Horizon. Può aiutare ad aggirare gli arresti anomali che si verificano avviando alcuni giochi.\n\nNel dubbio, lascia l'opzione disattivata.", + "GraphicsBackendThreadingTooltip": "Esegue i comandi del backend grafico su un secondo thread.\n\nVelocizza la compilazione degli shader, riduce lo stuttering e migliora le prestazioni sui driver grafici senza il supporto integrato al multithreading. Migliora leggermente le prestazioni sui driver che supportano il multithreading.\n\nNel dubbio, imposta l'opzione su Auto.", + "GalThreadingTooltip": "Esegue i comandi del backend grafico su un secondo thread.\n\nVelocizza la compilazione degli shader, riduce lo stuttering e migliora le prestazioni sui driver grafici senza il supporto integrato al multithreading. Migliora leggermente le prestazioni sui driver che supportano il multithreading.\n\nNel dubbio, imposta l'opzione su Auto.", + "ShaderCacheToggleTooltip": "Salva una cache degli shader su disco che riduce i fenomeni di stuttering nelle esecuzioni successive.\n\nNel dubbio, lascia l'opzione attiva.", + "ResolutionScaleTooltip": "Moltiplica la risoluzione di rendering del gioco.\n\nAlcuni giochi potrebbero non funzionare con questa opzione e sembrare pixelati anche quando la risoluzione è aumentata; per quei giochi, potrebbe essere necessario trovare mod che rimuovono l'anti-aliasing o che aumentano la risoluzione di rendering interna. Per quest'ultimo caso, probabilmente dovrai selezionare Nativo (1x).\n\nQuesta opzione può essere modificata mentre un gioco è in esecuzione facendo clic su \"Applica\" qui sotto; puoi semplicemente spostare la finestra delle impostazioni da parte e sperimentare fino a quando non trovi il tuo look preferito per un gioco.\n\nTenete a mente che 4x è troppo per praticamente qualsiasi configurazione.", "ResolutionScaleEntryTooltip": "Scala della risoluzione in virgola mobile, come 1,5. Le scale non integrali hanno maggiori probabilità di causare problemi o crash.", - "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", - "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", - "ShaderDumpPathTooltip": "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 modalità possibili sono 0-3", + "AnisotropyTooltip": "Livello del filtro anisotropico. Imposta su Auto per usare il valore richiesto dal gioco.", + "AspectRatioTooltip": "Proporzioni dello schermo applicate alla finestra di renderizzazione.\n\nCambialo solo se stai usando una mod di proporzioni per il tuo gioco, altrimenti la grafica verrà allungata.\n\nLasciare il 16:9 se incerto.", + "ShaderDumpPathTooltip": "Percorso di dump degli shader", + "FileLogTooltip": "Salva il log della console in un file su disco. Non influisce sulle prestazioni.", + "StubLogTooltip": "Stampa i messaggi di log relativi alle stub nella console. Non influisce sulle prestazioni.", + "InfoLogTooltip": "Stampa i messaggi di log informativi nella console. Non influisce sulle prestazioni.", + "WarnLogTooltip": "Stampa i messaggi di log relativi agli avvisi nella console. Non influisce sulle prestazioni.", + "ErrorLogTooltip": "Stampa i messaggi di log relativi agli errori nella console. Non influisce sulle prestazioni.", + "TraceLogTooltip": "Stampa i messaggi di log relativi al trace nella console. Non influisce sulle prestazioni.", + "GuestLogTooltip": "Stampa i messaggi di log del guest nella console. Non influisce sulle prestazioni.", + "FileAccessLogTooltip": "Stampa i messaggi di log relativi all'accesso ai file nella console.", + "FSAccessLogModeTooltip": "Attiva l'output dei log di accesso FS nella console. Le modalità possibili vanno da 0 a 3", "DeveloperOptionTooltip": "Usa con attenzione", - "OpenGlLogLevel": "Richiede livelli di log appropriati abilitati", - "DebugLogTooltip": "Attiva messaggi debug log", + "OpenGlLogLevel": "Richiede che i livelli di log appropriati siano abilitati", + "DebugLogTooltip": "Stampa i messaggi di log per il debug nella console.\n\nUsa questa opzione solo se specificatamente richiesto da un membro del team, dal momento che rende i log difficili da leggere e riduce le prestazioni dell'emulatore.", "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", + "OpenRyujinxLogsTooltip": "Apre la cartella dove vengono salvati i log", "ExitTooltip": "Esci da Ryujinx", - "OpenSettingsTooltip": "Apri finestra delle impostazioni", + "OpenSettingsTooltip": "Apri la 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", + "OpenAboutTooltip": "Apri la finestra delle informazioni", "GridSize": "Dimensione griglia", - "GridSizeTooltip": "Cambia la dimensione dei riquardi della griglia", - "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Portoghese Brasiliano", - "AboutRyujinxContributorsButtonHeader": "Vedi tutti i Contributors", + "GridSizeTooltip": "Cambia la dimensione dei riquadri della griglia", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Portoghese brasiliano", + "AboutRyujinxContributorsButtonHeader": "Mostra tutti i contributori", "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", - "GameListContextMenuManageModToolTip": "Manage Mods", - "GameListContextMenuManageMod": "Manage Mods", + "SettingsTabSystemEnableInternetAccess": "Attiva l'accesso a Internet da parte del guest/Modalità LAN", + "EnableInternetAccessTooltip": "Consente all'applicazione emulata di connettersi a Internet.\n\nI giochi che dispongono di una modalità LAN possono connettersi tra di loro quando questa opzione è abilitata e sono connessi alla stessa rete, comprese le console reali.\n\nQuesta opzione NON consente la connessione ai server di Nintendo. Potrebbe causare arresti anomali in alcuni giochi che provano a connettersi a Internet.\n\nNel dubbio, lascia l'opzione disattivata.", + "GameListContextMenuManageCheatToolTip": "Gestisci trucchi", + "GameListContextMenuManageCheat": "Gestisci trucchi", + "GameListContextMenuManageModToolTip": "Gestisci mod", + "GameListContextMenuManageMod": "Gestisci mod", "ControllerSettingsStickRange": "Raggio:", "DialogStopEmulationTitle": "Ryujinx - Ferma emulazione", "DialogStopEmulationMessage": "Sei sicuro di voler fermare l'emulazione?", @@ -519,14 +519,14 @@ "SettingsTabNetwork": "Rete", "SettingsTabNetworkConnection": "Connessione di rete", "SettingsTabCpuCache": "Cache CPU", - "SettingsTabCpuMemory": "Memoria CPU", - "DialogUpdaterFlatpakNotSupportedMessage": "Per favore aggiorna Ryujinx via FlatHub.", + "SettingsTabCpuMemory": "Modalità CPU", + "DialogUpdaterFlatpakNotSupportedMessage": "Aggiorna Ryujinx tramite FlatHub.", "UpdaterDisabledWarningTitle": "Updater disabilitato!", "ControllerSettingsRotate90": "Ruota in senso orario di 90°", - "IconSize": "Dimensioni icona", - "IconSizeTooltip": "Cambia le dimensioni dell'icona di un gioco", + "IconSize": "Dimensioni icone", + "IconSizeTooltip": "Cambia le dimensioni delle icone dei giochi", "MenuBarOptionsShowConsole": "Mostra console", - "ShaderCachePurgeError": "Errore nella pulizia della shader cache a {0}: {1}", + "ShaderCachePurgeError": "Errore nell'eliminazione della cache degli shader a {0}: {1}", "UserErrorNoKeys": "Chiavi non trovate", "UserErrorNoFirmware": "Firmware non trovato", "UserErrorFirmwareParsingFailed": "Errori di analisi del firmware", @@ -538,10 +538,10 @@ "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 nel percorso specificato.", "UserErrorUnknownDescription": "Si è verificato un errore sconosciuto!", - "UserErrorUndefinedDescription": "Si è verificato un errore sconosciuto! Non dovrebbe succedere, per favore contatta uno sviluppatore!", + "UserErrorUndefinedDescription": "Si è verificato un errore sconosciuto! Ciò non dovrebbe accadere, contatta uno sviluppatore!", "OpenSetupGuideMessage": "Apri la guida all'installazione", "NoUpdate": "Nessun aggiornamento", - "TitleUpdateVersionLabel": "Versione {0} - {1}", + "TitleUpdateVersionLabel": "Versione {0}", "RyujinxInfo": "Ryujinx - Info", "RyujinxConfirm": "Ryujinx - Conferma", "FileDialogAllTypes": "Tutti i tipi", @@ -549,18 +549,18 @@ "SwkbdMinCharacters": "Non può avere meno di {0} caratteri", "SwkbdMinRangeCharacters": "Può avere da {0} a {1} caratteri", "SoftwareKeyboard": "Tastiera software", - "SoftwareKeyboardModeNumeric": "Must be 0-9 or '.' only", + "SoftwareKeyboardModeNumeric": "Deve essere solo 0-9 o '.'", "SoftwareKeyboardModeAlphabet": "Deve essere solo caratteri non CJK", "SoftwareKeyboardModeASCII": "Deve essere solo testo ASCII", - "ControllerAppletControllers": "Supported Controllers:", - "ControllerAppletPlayers": "Players:", - "ControllerAppletDescription": "Your current configuration is invalid. Open settings and reconfigure your inputs.", - "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.", + "ControllerAppletControllers": "Controller supportati:", + "ControllerAppletPlayers": "Giocatori:", + "ControllerAppletDescription": "La configurazione corrente non è valida. Aprire le impostazioni e riconfigurare gli input.", + "ControllerAppletDocked": "Modalità TV attivata. Gli input della modalità portatile dovrebbero essere disabilitati.", "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...", + "UpdaterRenameFailed": "Non è stato possibile rinominare il file: {0}", + "UpdaterAddingFiles": "Aggiunta dei nuovi file...", + "UpdaterExtracting": "Estrazione dell'aggiornamento...", + "UpdaterDownloading": "Download dell'aggiornamento...", "Game": "Gioco", "Docked": "TV", "Handheld": "Portatile", @@ -568,20 +568,20 @@ "AboutPageDeveloperListMore": "{0} e altri ancora...", "ApiError": "Errore dell'API.", "LoadingHeading": "Caricamento di {0}", - "CompilingPPTC": "Compilazione PTC", - "CompilingShaders": "Compilazione Shaders", + "CompilingPPTC": "Compilazione PPTC", + "CompilingShaders": "Compilazione degli shader", "AllKeyboards": "Tutte le tastiere", "OpenFileDialogTitle": "Seleziona un file supportato da aprire", "OpenFolderDialogTitle": "Seleziona una cartella con un gioco estratto", "AllSupportedFormats": "Tutti i formati supportati", - "RyujinxUpdater": "Aggiornamento Ryujinx", + "RyujinxUpdater": "Aggiornamento di Ryujinx", "SettingsTabHotkeys": "Tasti di scelta rapida", "SettingsTabHotkeysHotkeys": "Tasti di scelta rapida", - "SettingsTabHotkeysToggleVsyncHotkey": "VSync:", - "SettingsTabHotkeysScreenshotHotkey": "Cattura Schermo:", - "SettingsTabHotkeysShowUiHotkey": "Mostra UI:", + "SettingsTabHotkeysToggleVsyncHotkey": "Attiva/disattiva VSync:", + "SettingsTabHotkeysScreenshotHotkey": "Cattura uno screenshot:", + "SettingsTabHotkeysShowUiHotkey": "Mostra l'interfaccia:", "SettingsTabHotkeysPauseHotkey": "Metti in pausa:", - "SettingsTabHotkeysToggleMuteHotkey": "Muta:", + "SettingsTabHotkeysToggleMuteHotkey": "Disattiva l'audio:", "ControllerMotionTitle": "Impostazioni dei sensori di movimento", "ControllerRumbleTitle": "Impostazioni di vibrazione", "SettingsSelectThemeFileDialogTitle": "Seleziona file del tema", @@ -593,63 +593,66 @@ "Writable": "Scrivibile", "SelectDlcDialogTitle": "Seleziona file dei DLC", "SelectUpdateDialogTitle": "Seleziona file di aggiornamento", - "SelectModDialogTitle": "Select mod directory", - "UserProfileWindowTitle": "Gestisci profili degli utenti", - "CheatWindowTitle": "Gestisci cheat dei giochi", - "DlcWindowTitle": "Gestisci DLC dei giochi", - "UpdateWindowTitle": "Gestisci aggiornamenti dei giochi", - "CheatWindowHeading": "Cheat disponibiili per {0} [{1}]", + "SelectModDialogTitle": "Seleziona cartella delle mod", + "UserProfileWindowTitle": "Gestione profili utente", + "CheatWindowTitle": "Gestione trucchi", + "DlcWindowTitle": "Gestisci DLC per {0} ({1})", + "UpdateWindowTitle": "Gestione aggiornamenti", + "CheatWindowHeading": "Trucchi disponibili per {0} [{1}]", "BuildId": "ID Build", "DlcWindowHeading": "DLC disponibili per {0} [{1}]", - "ModWindowHeading": "{0} Mod(s)", + "ModWindowHeading": "{0} mod", "UserProfilesEditProfile": "Modifica selezionati", "Cancel": "Annulla", "Save": "Salva", "Discard": "Scarta", - "Paused": "Paused", + "Paused": "In pausa", "UserProfilesSetProfileImage": "Imposta immagine profilo", - "UserProfileEmptyNameError": "È richiesto un nome", + "UserProfileEmptyNameError": "Il nome è obbligatorio", "UserProfileNoImageError": "Dev'essere impostata un'immagine profilo", - "GameUpdateWindowHeading": "Aggiornamenti disponibili per {0} [{1}]", - "SettingsTabHotkeysResScaleUpHotkey": "Aumentare la risoluzione:", - "SettingsTabHotkeysResScaleDownHotkey": "Diminuire la risoluzione:", + "GameUpdateWindowHeading": "Gestisci aggiornamenti per {0} ({1})", + "SettingsTabHotkeysResScaleUpHotkey": "Aumenta la risoluzione:", + "SettingsTabHotkeysResScaleDownHotkey": "Riduci la risoluzione:", "UserProfilesName": "Nome:", "UserProfilesUserId": "ID utente:", - "SettingsTabGraphicsBackend": "Backend grafica", - "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", - "SettingsEnableTextureRecompression": "Abilita Ricompressione Texture", - "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", + "SettingsTabGraphicsBackend": "Backend grafico", + "SettingsTabGraphicsBackendTooltip": "Seleziona il backend grafico che verrà utilizzato nell'emulatore.\n\nVulkan è nel complesso migliore per tutte le schede grafiche moderne, a condizione che i relativi driver siano aggiornati. Vulkan dispone anche di una compilazione degli shader più veloce (con minore stuttering) su tutte le marche di GPU.\n\nOpenGL può ottenere risultati migliori su vecchie GPU Nvidia, su vecchie GPU AMD su Linux, o su GPU con poca VRAM, anche se lo stuttering dovuto alla compilazione degli shader sarà maggiore.\n\nNel dubbio, scegli Vulkan. Seleziona OpenGL se la GPU non supporta Vulkan nemmeno con i driver grafici più recenti.", + "SettingsEnableTextureRecompression": "Attiva la ricompressione delle texture", + "SettingsEnableTextureRecompressionTooltip": "Comprime le texture ASTC per ridurre l'utilizzo di VRAM.\n\nI giochi che utilizzano questo formato di texture includono Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder e The Legend of Zelda: Tears of the Kingdom.\n\nLe schede grafiche con 4GiB o meno di VRAM probabilmente si bloccheranno ad un certo punto durante l'esecuzione di questi giochi.\n\nAttiva questa opzione solo se sei a corto di VRAM nei giochi sopra menzionati. Nel dubbio, lascia l'opzione disattivata.", "SettingsTabGraphicsPreferredGpu": "GPU preferita", "SettingsTabGraphicsPreferredGpuTooltip": "Seleziona la scheda grafica che verrà usata con la backend grafica Vulkan.\n\nNon influenza la GPU che userà OpenGL.\n\nImposta la GPU contrassegnata come \"dGPU\" se non sei sicuro. Se non ce n'è una, lascia intatta quest'impostazione.", "SettingsAppRequiredRestartMessage": "È richiesto un riavvio di Ryujinx", "SettingsGpuBackendRestartMessage": "Le impostazioni della backend grafica o della GPU sono state modificate. Questo richiederà un riavvio perché le modifiche siano applicate", "SettingsGpuBackendRestartSubMessage": "Vuoi riavviare ora?", "RyujinxUpdaterMessage": "Vuoi aggiornare Ryujinx all'ultima versione?", - "SettingsTabHotkeysVolumeUpHotkey": "Aumentare il volume:", - "SettingsTabHotkeysVolumeDownHotkey": "Diminuire il volume:", - "SettingsEnableMacroHLE": "Abilita Macro HLE", - "SettingsEnableMacroHLETooltip": "Emulazione di alto livello del codice Macro GPU.\n\nMigliora le prestazioni, ma può causare anomalie grafiche in alcuni giochi.\n\nLasciare ON se non sei sicuro.", - "SettingsEnableColorSpacePassthrough": "Spazio colore passante", - "SettingsEnableColorSpacePassthroughTooltip": "Indica al backend Vulkan di passare le informazioni sul colore senza specificare uno spazio colore. Per gli utenti con schermi ad ampia gamma, questo può risultare in colori più vivaci, al costo della correttezza del colore.", + "SettingsTabHotkeysVolumeUpHotkey": "Alza il volume:", + "SettingsTabHotkeysVolumeDownHotkey": "Abbassa il volume:", + "SettingsEnableMacroHLE": "Attiva HLE macro", + "SettingsEnableMacroHLETooltip": "Emulazione di alto livello del codice macro della GPU.\n\nMigliora le prestazioni, ma può causare anomalie grafiche in alcuni giochi.\n\nNel dubbio, lascia l'opzione attiva.", + "SettingsEnableColorSpacePassthrough": "Passthrough dello spazio dei colori", + "SettingsEnableColorSpacePassthroughTooltip": "Indica al backend Vulkan di passare le informazioni sul colore senza specificare uno spazio dei colori. Per gli utenti con schermi ad ampia gamma, ciò può rendere i colori più vivaci, sacrificando la correttezza del colore.", "VolumeShort": "Vol", "UserProfilesManageSaves": "Gestisci i salvataggi", "DeleteUserSave": "Vuoi eliminare il salvataggio utente per questo gioco?", "IrreversibleActionNote": "Questa azione non è reversibile.", - "SaveManagerHeading": "Gestisci i salvataggi per {0}", - "SaveManagerTitle": "Gestione Salvataggi", + "SaveManagerHeading": "Gestisci salvataggi per {0} ({1})", + "SaveManagerTitle": "Gestione salvataggi", "Name": "Nome", "Size": "Dimensione", "Search": "Cerca", - "UserProfilesRecoverLostAccounts": "Recupera il tuo account", + "UserProfilesRecoverLostAccounts": "Recupera account persi", "Recover": "Recupera", "UserProfilesRecoverHeading": "Sono stati trovati dei salvataggi per i seguenti account", "UserProfilesRecoverEmptyList": "Nessun profilo da recuperare", - "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", + "GraphicsAATooltip": "Applica anti-aliasing al rendering del gioco.\n\nFXAA sfocerà la maggior parte dell'immagine, mentre SMAA tenterà di trovare bordi frastagliati e lisciarli.\n\nNon si consiglia di usarlo in combinazione con il filtro di scala FSR.\n\nQuesta opzione può essere modificata mentre un gioco è in esecuzione facendo clic su \"Applica\" qui sotto; puoi semplicemente spostare la finestra delle impostazioni da parte e sperimentare fino a quando non trovi il tuo look preferito per un gioco.\n\nLasciare su Nessuno se incerto.", "GraphicsAALabel": "Anti-Aliasing:", "GraphicsScalingFilterLabel": "Filtro di scala:", - "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterTooltip": "Scegli il filtro di scaling che verrà applicato quando si utilizza o scaling di risoluzione.\n\nBilineare funziona bene per i giochi 3D ed è un'opzione predefinita affidabile.\n\nNearest è consigliato per i giochi in pixel art.\n\nFSR 1.0 è solo un filtro di nitidezza, non raccomandato per l'uso con FXAA o SMAA.\n\nQuesta opzione può essere modificata mentre un gioco è in esecuzione facendo clic su \"Applica\" qui sotto; puoi semplicemente spostare la finestra delle impostazioni da parte e sperimentare fino a quando non trovi il tuo look preferito per un gioco.\n\nLasciare su Bilineare se incerto.", + "GraphicsScalingFilterBilinear": "Bilineare", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "Livello", - "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", + "GraphicsScalingFilterLevelTooltip": "Imposta il livello di nitidezza di FSR 1.0. Valori più alti comportano una maggiore nitidezza.", "SmaaLow": "SMAA Basso", "SmaaMedium": "SMAA Medio", "SmaaHigh": "SMAA Alto", @@ -657,12 +660,14 @@ "UserEditorTitle": "Modificare L'Utente", "UserEditorTitleCreate": "Crea Un Utente", "SettingsTabNetworkInterface": "Interfaccia di rete:", - "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", + "NetworkInterfaceTooltip": "L'interfaccia di rete utilizzata per le funzionalità LAN/LDN.\n\nIn combinazione con una VPN o XLink Kai e un gioco che supporta la modalità LAN, questa opzione può essere usata per simulare la connessione alla stessa rete attraverso Internet.\n\nNel dubbio, lascia l'opzione su Predefinito.", "NetworkInterfaceDefault": "Predefinito", - "PackagingShaders": "Comprimendo shader", + "PackagingShaders": "Salvataggio degli shader", "AboutChangelogButton": "Visualizza changelog su GitHub", "AboutChangelogButtonTooltipMessage": "Clicca per aprire il changelog per questa versione nel tuo browser predefinito.", - "SettingsTabNetworkMultiplayer": "Multiplayer", - "MultiplayerMode": "Mode:", - "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." + "SettingsTabNetworkMultiplayer": "Multigiocatore", + "MultiplayerMode": "Modalità:", + "MultiplayerModeTooltip": "Cambia la modalità multigiocatore LDN.\n\nLdnMitm modificherà la funzionalità locale wireless/local play nei giochi per funzionare come se fosse in modalità LAN, consentendo connessioni locali sulla stessa rete con altre istanze di Ryujinx e console Nintendo Switch modificate che hanno il modulo ldn_mitm installato.\n\nLa modalità multigiocatore richiede che tutti i giocatori usino la stessa versione del gioco (es. Super Smash Bros. Ultimate v13.0.1 non può connettersi con la v13.0.0).\n\nNel dubbio, lascia l'opzione su Disabilitato.", + "MultiplayerModeDisabled": "Disabilitato", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/ja_JP.json b/src/Ryujinx/Assets/Locales/ja_JP.json index b57d15e2ee..8790135e01 100644 --- a/src/Ryujinx/Assets/Locales/ja_JP.json +++ b/src/Ryujinx/Assets/Locales/ja_JP.json @@ -17,7 +17,7 @@ "MenuBarOptions": "オプション(_O)", "MenuBarOptionsToggleFullscreen": "全画面切り替え", "MenuBarOptionsStartGamesInFullscreen": "全画面モードでゲームを開始", - "MenuBarOptionsStopEmulation": "エミュレーションを停止", + "MenuBarOptionsStopEmulation": "エミュレーションを中止", "MenuBarOptionsSettings": "設定(_S)", "MenuBarOptionsManageUserProfiles": "ユーザプロファイルを管理(_M)", "MenuBarActions": "アクション(_A)", @@ -72,11 +72,11 @@ "GameListContextMenuExtractDataLogoToolTip": "現在のアプリケーション設定(アップデート含む)からロゴセクションを展開します", "GameListContextMenuCreateShortcut": "アプリケーションのショートカットを作成", "GameListContextMenuCreateShortcutToolTip": "選択したアプリケーションを起動するデスクトップショートカットを作成します", - "GameListContextMenuCreateShortcutToolTipMacOS": "Create a shortcut in macOS's Applications folder that launches the selected Application", + "GameListContextMenuCreateShortcutToolTipMacOS": "選択したアプリケーションを起動する ショートカットを macOS の Applications フォルダに作成します", "GameListContextMenuOpenModsDirectory": "Modディレクトリを開く", - "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", + "GameListContextMenuOpenModsDirectoryToolTip": "アプリケーションの Mod データを格納するディレクトリを開きます", "GameListContextMenuOpenSdModsDirectory": "Atmosphere Mods ディレクトリを開く", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", + "GameListContextMenuOpenSdModsDirectoryToolTip": "アプリケーションの Mod データを格納する SD カードの Atmosphere ディレクトリを開きます. 実際のハードウェア用に作成された Mod データに有用です.", "StatusBarGamesLoaded": "{0}/{1} ゲーム", "StatusBarSystemVersion": "システムバージョン: {0}", "LinuxVmMaxMapCountDialogTitle": "メモリマッピング上限値が小さすぎます", @@ -385,21 +385,21 @@ "DialogControllerSettingsModifiedConfirmMessage": "現在のコントローラ設定が更新されました.", "DialogControllerSettingsModifiedConfirmSubMessage": "セーブしますか?", "DialogLoadFileErrorMessage": "{0}. エラー発生ファイル: {1}", - "DialogModAlreadyExistsMessage": "Mod already exists", - "DialogModInvalidMessage": "The specified directory does not contain a mod!", - "DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!", + "DialogModAlreadyExistsMessage": "Modはすでに存在します", + "DialogModInvalidMessage": "指定したディレクトリにはmodが含まれていません!", + "DialogModDeleteNoParentMessage": "削除に失敗しました: Mod \"{0}\" の親ディレクトリが見つかりませんでした!", "DialogDlcNoDlcErrorMessage": "選択されたファイルはこのタイトル用の DLC ではありません!", "DialogPerformanceCheckLoggingEnabledMessage": "トレースロギングを有効にします. これは開発者のみに有用な機能です.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "パフォーマンス最適化のためには,トレースロギングを無効にすることを推奨します. トレースロギングを無効にしてよろしいですか?", "DialogPerformanceCheckShaderDumpEnabledMessage": "シェーダーダンプを有効にします. これは開発者のみに有用な機能です.", "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "パフォーマンス最適化のためには, シェーダーダンプを無効にすることを推奨します. シェーダーダンプを無効にしてよろしいですか?", "DialogLoadAppGameAlreadyLoadedMessage": "ゲームはすでにロード済みです", - "DialogLoadAppGameAlreadyLoadedSubMessage": "別のゲームを起動する前に, エミュレーションを停止またはエミュレータを閉じてください.", + "DialogLoadAppGameAlreadyLoadedSubMessage": "別のゲームを起動する前に, エミュレーションを中止またはエミュレータを閉じてください.", "DialogUpdateAddUpdateErrorMessage": "選択されたファイルはこのタイトル用のアップデートではありません!", "DialogSettingsBackendThreadingWarningTitle": "警告 - バックエンドスレッディング", "DialogSettingsBackendThreadingWarningMessage": "このオプションの変更を完全に適用するには Ryujinx の再起動が必要です. プラットフォームによっては, Ryujinx のものを使用する前に手動でドライバ自身のマルチスレッディングを無効にする必要があるかもしれません.", - "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", - "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionWarningMessage": "以下のModを削除しようとしています: {0}\n\n続行してもよろしいですか?", + "DialogModManagerDeletionAllWarningMessage": "このタイトルの Mod をすべて削除しようとしています.\n\n続行してもよろしいですか?", "SettingsTabGraphicsFeaturesOptions": "機能", "SettingsTabGraphicsBackendMultithreading": "グラフィックスバックエンドのマルチスレッド実行:", "CommonAuto": "自動", @@ -408,7 +408,7 @@ "InputDialogYes": "はい", "InputDialogNo": "いいえ", "DialogProfileInvalidProfileNameErrorMessage": "プロファイル名に無効な文字が含まれています. 再度試してみてください.", - "MenuBarOptionsPauseEmulation": "中断", + "MenuBarOptionsPauseEmulation": "一時停止", "MenuBarOptionsResumeEmulation": "再開", "AboutUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx のウェブサイトを開きます.", "AboutDisclaimerMessage": "Ryujinx は Nintendo™ および\nそのパートナー企業とは一切関係ありません.", @@ -452,13 +452,13 @@ "CustomThemePathTooltip": "カスタム GUI テーマのパスです", "CustomThemeBrowseTooltip": "カスタム GUI テーマを参照します", "DockModeToggleTooltip": "有効にすると,ドッキングされた Nintendo Switch をエミュレートします.多くのゲームではグラフィックス品質が向上します.\n無効にすると,携帯モードの Nintendo Switch をエミュレートします.グラフィックスの品質は低下します.\n\nドッキングモード有効ならプレイヤー1の,無効なら携帯の入力を設定してください.\n\nよくわからない場合はオンのままにしてください.", - "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", - "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", + "DirectKeyboardTooltip": "直接キーボード アクセス (HID) のサポートです. テキスト入力デバイスとしてキーボードへのゲームアクセスを提供します.\n\nSwitchハードウェアでキーボードの使用をネイティブにサポートしているゲームでのみ動作します.\n\nわからない場合はオフのままにしてください.", + "DirectMouseTooltip": "直接マウスアクセス (HID) のサポートです. ポインティングデバイスとしてマウスへのゲームアクセスを提供します.\n\nSwitchハードウェアでマウスの使用をネイティブにサポートしているゲームでのみ動作します.\n\n有効にしている場合, タッチスクリーン機能は動作しない場合があります.\n\nわからない場合はオフのままにしてください.", "RegionTooltip": "システムの地域を変更します", "LanguageTooltip": "システムの言語を変更します", "TimezoneTooltip": "システムのタイムゾーンを変更します", "TimeTooltip": "システムの時刻を変更します", - "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", + "VSyncToggleTooltip": "エミュレートされたゲーム機の垂直同期です. 多くのゲームにおいて, フレームリミッタとして機能します. 無効にすると, ゲームが高速で実行されたり, ロード中に時間がかかったり, 止まったりすることがあります.\n\n設定したホットキー(デフォルトではF1)で, ゲーム内で切り替え可能です. 無効にする場合は, この操作を行うことをおすすめします.\n\nよくわからない場合はオンのままにしてください.", "PptcToggleTooltip": "翻訳されたJIT関数をセーブすることで, ゲームをロードするたびに毎回翻訳する処理を不要とします.\n\n一度ゲームを起動すれば,二度目以降の起動時遅延を大きく軽減できます.\n\nよくわからない場合はオンのままにしてください.", "FsIntegrityToggleTooltip": "ゲーム起動時にファイル破損をチェックし,破損が検出されたらログにハッシュエラーを表示します..\n\nパフォーマンスには影響なく, トラブルシューティングに役立ちます.\n\nよくわからない場合はオンのままにしてください.", "AudioBackendTooltip": "音声レンダリングに使用するバックエンドを変更します.\n\nSDL2 が優先され, OpenAL と SoundIO はフォールバックとして使用されます. ダミーは音声出力しません.\n\nよくわからない場合は SDL2 を設定してください.", @@ -472,10 +472,10 @@ "GraphicsBackendThreadingTooltip": "グラフィックスバックエンドのコマンドを別スレッドで実行します.\n\nシェーダのコンパイルを高速化し, 遅延を軽減し, マルチスレッド非対応の GPU ドライバにおいてパフォーマンスを改善します. マルチスレッド対応のドライバでも若干パフォーマンス改善が見られます.\n\nよくわからない場合は自動に設定してください.", "GalThreadingTooltip": "グラフィックスバックエンドのコマンドを別スレッドで実行します.\n\nシェーダのコンパイルを高速化し, 遅延を軽減し, マルチスレッド非対応の GPU ドライバにおいてパフォーマンスを改善します. マルチスレッド対応のドライバでも若干パフォーマンス改善が見られます.\n\nよくわからない場合は自動に設定してください.", "ShaderCacheToggleTooltip": "ディスクシェーダーキャッシュをセーブし,次回以降の実行時遅延を軽減します.\n\nよくわからない場合はオンのままにしてください.", - "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", + "ResolutionScaleTooltip": "ゲームのレンダリング解像度倍率を設定します.\n\n解像度を上げてもピクセルのように見えるゲームもあります. そのようなゲームでは, アンチエイリアスを削除するか, 内部レンダリング解像度を上げる mod を見つける必要があるかもしれません. その場合, ようなゲームでは、ネイティブを選択してください.\n\nこのオプションはゲーム実行中に下の「適用」をクリックすることで変更できます. 設定ウィンドウを脇に移動して, ゲームが好みの表示になるよう試してみてください.\n\nどのような設定でも, \"4x\" はやり過ぎであることを覚えておいてください.", "ResolutionScaleEntryTooltip": "1.5 のような整数でない倍率を指定すると,問題が発生したりクラッシュしたりする場合があります.", - "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", - "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", + "AnisotropyTooltip": "異方性フィルタリングのレベルです. ゲームが要求する値を使用する場合は「自動」を設定してください.", + "AspectRatioTooltip": "レンダリングウインドウに適用するアスペクト比です.\n\nゲームにアスペクト比を変更する mod を使用している場合のみ変更してください.\n\nわからない場合は16:9のままにしておいてください.\n", "ShaderDumpPathTooltip": "グラフィックス シェーダー ダンプのパスです", "FileLogTooltip": "コンソール出力されるログをディスク上のログファイルにセーブします. パフォーマンスには影響を与えません.", "StubLogTooltip": "stub ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", @@ -496,7 +496,7 @@ "ExitTooltip": "Ryujinx を終了します", "OpenSettingsTooltip": "設定ウインドウを開きます", "OpenProfileManagerTooltip": "ユーザプロファイル管理ウインドウを開きます", - "StopEmulationTooltip": "ゲームのエミュレーションを停止してゲーム選択画面に戻ります", + "StopEmulationTooltip": "ゲームのエミュレーションを中止してゲーム選択画面に戻ります", "CheckUpdatesTooltip": "Ryujinx のアップデートを確認します", "OpenAboutTooltip": "Ryujinx についてのウインドウを開きます", "GridSize": "グリッドサイズ", @@ -509,11 +509,11 @@ "EnableInternetAccessTooltip": "エミュレートしたアプリケーションをインターネットに接続できるようにします.\n\nLAN モードを持つゲーム同士は,この機能を有効にして同じアクセスポイントに接続すると接続できます. 実機も含まれます.\n\n任天堂のサーバーには接続できません. インターネットに接続しようとすると,特定のゲームでクラッシュすることがあります.\n\nよくわからない場合はオフのままにしてください.", "GameListContextMenuManageCheatToolTip": "チートを管理します", "GameListContextMenuManageCheat": "チートを管理", - "GameListContextMenuManageModToolTip": "Manage Mods", + "GameListContextMenuManageModToolTip": "Modを管理します", "GameListContextMenuManageMod": "Manage Mods", "ControllerSettingsStickRange": "範囲:", - "DialogStopEmulationTitle": "Ryujinx - エミュレーションを停止", - "DialogStopEmulationMessage": "エミュレーションを停止してよろしいですか?", + "DialogStopEmulationTitle": "Ryujinx - エミュレーションを中止", + "DialogStopEmulationMessage": "エミュレーションを中止してよろしいですか?", "SettingsTabCpu": "CPU", "SettingsTabAudio": "音声", "SettingsTabNetwork": "ネットワーク", @@ -554,8 +554,8 @@ "SoftwareKeyboardModeASCII": "ASCII文字列のみ", "ControllerAppletControllers": "サポートされているコントローラ:", "ControllerAppletPlayers": "プレイヤー:", - "ControllerAppletDescription": "Your current configuration is invalid. Open settings and reconfigure your inputs.", - "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.", + "ControllerAppletDescription": "現在の設定は無効です. 設定を開いて入力を再設定してください.", + "ControllerAppletDocked": "ドッキングモードが設定されています. 携帯コントロールは無効にする必要があります.", "UpdaterRenaming": "古いファイルをリネーム中...", "UpdaterRenameFailed": "ファイルをリネームできませんでした: {0}", "UpdaterAddingFiles": "新規ファイルを追加中...", @@ -580,7 +580,7 @@ "SettingsTabHotkeysToggleVsyncHotkey": "VSync 切り替え:", "SettingsTabHotkeysScreenshotHotkey": "スクリーンショット:", "SettingsTabHotkeysShowUiHotkey": "UI表示:", - "SettingsTabHotkeysPauseHotkey": "中断:", + "SettingsTabHotkeysPauseHotkey": "一時停止:", "SettingsTabHotkeysToggleMuteHotkey": "ミュート:", "ControllerMotionTitle": "モーションコントロール設定", "ControllerRumbleTitle": "振動設定", @@ -593,7 +593,7 @@ "Writable": "書き込み可能", "SelectDlcDialogTitle": "DLC ファイルを選択", "SelectUpdateDialogTitle": "アップデートファイルを選択", - "SelectModDialogTitle": "Select mod directory", + "SelectModDialogTitle": "modディレクトリを選択", "UserProfileWindowTitle": "ユーザプロファイルを管理", "CheatWindowTitle": "チート管理", "DlcWindowTitle": "DLC 管理", @@ -606,7 +606,7 @@ "Cancel": "キャンセル", "Save": "セーブ", "Discard": "破棄", - "Paused": "Paused", + "Paused": "一時停止中", "UserProfilesSetProfileImage": "プロファイル画像を設定", "UserProfileEmptyNameError": "名称が必要です", "UserProfileNoImageError": "プロファイル画像が必要です", @@ -616,9 +616,9 @@ "UserProfilesName": "名称:", "UserProfilesUserId": "ユーザID:", "SettingsTabGraphicsBackend": "グラフィックスバックエンド", - "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", + "SettingsTabGraphicsBackendTooltip": "エミュレーションに使用するグラフィックスバックエンドを選択します.\n\nVulkanは, 最近のグラフィックカードでドライバが最新であれば, 全体的に優れています. すべてのGPUベンダーで, シェーダーコンパイルがより高速で, スタッタリングが少ないのが特徴です.\n\n古いNvidia GPU, Linuxでの古いAMD GPU, VRAMの少ないGPUなどでは, OpenGLの方が良い結果が得られるかもしれません. ですが, シェーダーコンパイルのスタッターは大きくなります.\n\n不明な場合はVulkanに設定してください。最新のグラフィックドライバでもVulkanをサポートしていないGPUの場合は, OpenGLに設定してください.", "SettingsEnableTextureRecompression": "テクスチャの再圧縮を有効にする", - "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", + "SettingsEnableTextureRecompressionTooltip": "VRAM使用量を減らすためにASTCテクスチャを圧縮します.\n\nこのテクスチャフォーマットを使用するゲームには, Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder, The Legend of Zelda: Tears of the Kingdomが含まれます.\n\nVRAMが4GB以下のグラフィックカードでは, これらのゲームを実行中にクラッシュする可能性があります.\n\n前述のゲームでVRAMが不足している場合のみ有効にしてください. 不明な場合はオフにしてください.", "SettingsTabGraphicsPreferredGpu": "優先使用するGPU", "SettingsTabGraphicsPreferredGpuTooltip": "Vulkanグラフィックスバックエンドで使用されるグラフィックスカードを選択します.\n\nOpenGLが使用するGPUには影響しません.\n\n不明な場合は, \"dGPU\" としてフラグが立っているGPUに設定します. ない場合はそのままにします.", "SettingsAppRequiredRestartMessage": "Ryujinx の再起動が必要です", @@ -644,12 +644,15 @@ "Recover": "復旧", "UserProfilesRecoverHeading": "以下のアカウントのセーブデータが見つかりました", "UserProfilesRecoverEmptyList": "復元するプロファイルはありません", - "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", + "GraphicsAATooltip": "ゲームレンダリングにアンチエイリアスを適用します.\n\nFXAAは画像の大部分をぼかし, SMAAはギザギザのエッジを見つけて滑らかにします.\n\nFSRスケーリングフィルタとの併用は推奨しません.\n\nこのオプションは, ゲーム実行中に下の「適用」をクリックして変更できます. 設定ウィンドウを脇に移動し, ゲームが好みの表示になるように試してみてください.\n\n不明な場合は「なし」のままにしておいてください.", "GraphicsAALabel": "アンチエイリアス:", "GraphicsScalingFilterLabel": "スケーリングフィルタ:", - "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterTooltip": "解像度変更時に適用されるスケーリングフィルタを選択します.\n\nBilinearは3Dゲームに適しており, 安全なデフォルトオプションです.\n\nピクセルアートゲームにはNearestを推奨します.\n\nFSR 1.0は単なるシャープニングフィルタであり, FXAAやSMAAとの併用は推奨されません.\n\nこのオプションは, ゲーム実行中に下の「適用」をクリックすることで変更できます. 設定ウィンドウを脇に移動し, ゲームが好みの表示になるように試してみてください.\n\n不明な場合はBilinearのままにしておいてください.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "レベル", - "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", + "GraphicsScalingFilterLevelTooltip": "FSR 1.0のシャープ化レベルを設定します. 高い値ほどシャープになります.", "SmaaLow": "SMAA Low", "SmaaMedium": "SMAA Medium", "SmaaHigh": "SMAA High", @@ -657,12 +660,14 @@ "UserEditorTitle": "ユーザを編集", "UserEditorTitleCreate": "ユーザを作成", "SettingsTabNetworkInterface": "ネットワークインタフェース:", - "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", + "NetworkInterfaceTooltip": "LAN/LDN機能に使用されるネットワークインタフェースです.\n\nVPNやXLink Kai、LAN対応のゲームと併用することで, インターネット上の同一ネットワーク接続になりすますことができます.\n\n不明な場合はデフォルトのままにしてください.", "NetworkInterfaceDefault": "デフォルト", "PackagingShaders": "シェーダーを構築中", "AboutChangelogButton": "GitHub で更新履歴を表示", "AboutChangelogButtonTooltipMessage": "クリックして, このバージョンの更新履歴をデフォルトのブラウザで開きます.", "SettingsTabNetworkMultiplayer": "マルチプレイヤー", "MultiplayerMode": "モード:", - "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." + "MultiplayerModeTooltip": "LDNマルチプレイヤーモードを変更します.\n\nldn_mitmモジュールがインストールされた, 他のRyujinxインスタンスや,ハックされたNintendo Switchコンソールとのローカル/同一ネットワーク接続を可能にします.\n\nマルチプレイでは, すべてのプレイヤーが同じゲームバージョンである必要があります(例:Super Smash Bros. Ultimate v13.0.1はv13.0.0に接続できません).\n\n不明な場合は「無効」のままにしてください.", + "MultiplayerModeDisabled": "無効", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/ko_KR.json b/src/Ryujinx/Assets/Locales/ko_KR.json index 133a51b630..0cd054cab2 100644 --- a/src/Ryujinx/Assets/Locales/ko_KR.json +++ b/src/Ryujinx/Assets/Locales/ko_KR.json @@ -1,8 +1,8 @@ { "Language": "한국어", "MenuBarFileOpenApplet": "애플릿 열기", - "MenuBarFileOpenAppletOpenMiiAppletToolTip": "독립 실행형 모드에서 Mii 편집기 애플릿 열기", - "SettingsTabInputDirectMouseAccess": "직접 마우스 접속", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "독립 실행형 모드로 Mii 편집기 애플릿 열기", + "SettingsTabInputDirectMouseAccess": "다이렉트 마우스 접근", "SettingsTabSystemMemoryManagerMode": "메모리 관리자 모드:", "SettingsTabSystemMemoryManagerModeSoftware": "소프트웨어", "SettingsTabSystemMemoryManagerModeHost": "호스트 (빠름)", @@ -72,11 +72,11 @@ "GameListContextMenuExtractDataLogoToolTip": "응용프로그램의 현재 구성에서 로고 섹션 추출 (업데이트 포함)", "GameListContextMenuCreateShortcut": "애플리케이션 바로 가기 만들기", "GameListContextMenuCreateShortcutToolTip": "선택한 애플리케이션을 실행하는 바탕 화면 바로 가기를 만듭니다.", - "GameListContextMenuCreateShortcutToolTipMacOS": "Create a shortcut in macOS's Applications folder that launches the selected Application", - "GameListContextMenuOpenModsDirectory": "Open Mods Directory", - "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", - "GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", + "GameListContextMenuCreateShortcutToolTipMacOS": "해당 게임을 실행할 수 있는 바로가기를 macOS의 응용 프로그램 폴더에 추가합니다.", + "GameListContextMenuOpenModsDirectory": "Mod 디렉터리 열기", + "GameListContextMenuOpenModsDirectoryToolTip": "해당 게임의 Mod가 저장된 디렉터리 열기", + "GameListContextMenuOpenSdModsDirectory": "Atmosphere Mod 디렉터리 열기", + "GameListContextMenuOpenSdModsDirectoryToolTip": "해당 게임의 Mod가 포함된 대체 SD 카드 Atmosphere 디렉터리를 엽니다. 실제 하드웨어용으로 패키징된 Mod에 유용합니다.", "StatusBarGamesLoaded": "{0}/{1}개의 게임 불러옴", "StatusBarSystemVersion": "시스템 버전 : {0}", "LinuxVmMaxMapCountDialogTitle": "감지된 메모리 매핑의 하한선", @@ -155,7 +155,7 @@ "SettingsTabGraphicsResolutionScaleNative": "원본(720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2배(1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3배(2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Not recommended)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (권장하지 않음)", "SettingsTabGraphicsAspectRatio": "종횡비 :", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -297,9 +297,9 @@ "GameListContextMenuRunApplication": "응용프로그램 실행", "GameListContextMenuToggleFavorite": "즐겨찾기 전환", "GameListContextMenuToggleFavoriteToolTip": "게임 즐겨찾기 상태 전환", - "SettingsTabGeneralTheme": "Theme:", - "SettingsTabGeneralThemeDark": "Dark", - "SettingsTabGeneralThemeLight": "Light", + "SettingsTabGeneralTheme": "테마:", + "SettingsTabGeneralThemeDark": "어두운 테마", + "SettingsTabGeneralThemeLight": "밝은 테마", "ControllerSettingsConfigureGeneral": "구성", "ControllerSettingsRumble": "진동", "ControllerSettingsRumbleStrongMultiplier": "강력한 진동 증폭기", @@ -384,10 +384,10 @@ "DialogUserProfileUnsavedChangesSubMessage": "변경사항을 저장하지 않으시겠습니까?", "DialogControllerSettingsModifiedConfirmMessage": "현재 컨트롤러 설정이 업데이트되었습니다.", "DialogControllerSettingsModifiedConfirmSubMessage": "저장하겠습니까?", - "DialogLoadFileErrorMessage": "{0}. Errored File: {1}", - "DialogModAlreadyExistsMessage": "Mod already exists", - "DialogModInvalidMessage": "The specified directory does not contain a mod!", - "DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!", + "DialogLoadFileErrorMessage": "{0}. 오류 발생 파일 : {1}", + "DialogModAlreadyExistsMessage": "Mod가 이미 존재합니다.", + "DialogModInvalidMessage": "지정된 디렉터리에 Mod가 없습니다!", + "DialogModDeleteNoParentMessage": "삭제 실패: \"{0}\" Mod의 상위 디렉터리를 찾을 수 없습니다!", "DialogDlcNoDlcErrorMessage": "지정된 파일에 선택한 타이틀에 대한 DLC가 포함되어 있지 않습니다!", "DialogPerformanceCheckLoggingEnabledMessage": "개발자만 사용하도록 설계된 추적 로그 기록이 활성화되어 있습니다.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "최적의 성능을 위해 추적 로그 생성을 비활성화하는 것이 좋습니다. 지금 추적 로그 기록을 비활성화하겠습니까?", @@ -398,8 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "지정된 파일에 선택한 제목에 대한 업데이트가 포함되어 있지 않습니다!", "DialogSettingsBackendThreadingWarningTitle": "경고 - 후단부 스레딩", "DialogSettingsBackendThreadingWarningMessage": "변경 사항을 완전히 적용하려면 이 옵션을 변경한 후, Ryujinx를 다시 시작해야 합니다. 플랫폼에 따라 Ryujinx를 사용할 때 드라이버 자체의 멀티스레딩을 수동으로 비활성화해야 할 수도 있습니다.", - "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", - "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionWarningMessage": "해당 Mod를 삭제하려고 합니다: {0}\n\n정말로 삭제하시겠습니까?", + "DialogModManagerDeletionAllWarningMessage": "해당 타이틀에 대한 모든 Mod들을 삭제하려고 합니다.\n\n정말로 삭제하시겠습니까?", "SettingsTabGraphicsFeaturesOptions": "기능", "SettingsTabGraphicsBackendMultithreading": "그래픽 후단부 멀티스레딩 :", "CommonAuto": "자동", @@ -434,7 +434,7 @@ "DlcManagerRemoveAllButton": "모두 제거", "DlcManagerEnableAllButton": "모두 활성화", "DlcManagerDisableAllButton": "모두 비활성화", - "ModManagerDeleteAllButton": "Delete All", + "ModManagerDeleteAllButton": "모두 삭제", "MenuBarOptionsChangeLanguage": "언어 변경", "MenuBarShowFileTypes": "파일 유형 표시", "CommonSort": "정렬", @@ -452,13 +452,13 @@ "CustomThemePathTooltip": "사용자 정의 GUI 테마 경로", "CustomThemeBrowseTooltip": "사용자 정의 GUI 테마 찾아보기", "DockModeToggleTooltip": "독 모드에서는 에뮬레이트된 시스템이 도킹된 닌텐도 스위치처럼 작동합니다. 이것은 대부분의 게임에서 그래픽 품질을 향상시킵니다. 반대로 이 기능을 비활성화하면 에뮬레이트된 시스템이 휴대용 닌텐도 스위치처럼 작동하여 그래픽 품질이 저하됩니다.\n\n독 모드를 사용하려는 경우 플레이어 1의 컨트롤을 구성하세요. 휴대 모드를 사용하려는 경우 휴대용 컨트롤을 구성하세요.\n\n확실하지 않으면 켜 두세요.", - "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", - "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", + "DirectKeyboardTooltip": "다이렉트 키보드 접근(HID)은 게임에서 사용자의 키보드를 텍스트 입력 장치로 사용할 수 있게끔 제공합니다.\n\n스위치 하드웨어에서 키보드 사용을 네이티브로 지원하는 게임에서만 작동합니다.\n\n이 옵션에 대해 잘 모른다면 끄기를 권장합니다.", + "DirectMouseTooltip": "다이렉트 마우스 접근(HID)은 게임에서 사용자의 마우스를 포인터 장치로 사용할 수 있게끔 제공합니다.\n\n스위치 하드웨어에서 마우스 사용을 네이티브로 지원하는 극히 일부 게임에서만 작동합니다.\n\n이 옵션이 활성화된 경우, 터치 스크린 기능이 작동하지 않을 수 있습니다.\n\n이 옵션에 대해 잘 모른다면 끄기를 권장합니다.", "RegionTooltip": "시스템 지역 변경", "LanguageTooltip": "시스템 언어 변경", "TimezoneTooltip": "시스템 시간대 변경", "TimeTooltip": "시스템 시간 변경", - "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", + "VSyncToggleTooltip": "에뮬레이트된 콘솔의 수직 동기화. 기본적으로 대부분의 게임에 대한 프레임 제한 장치로, 비활성화시 게임이 더 빠른 속도로 실행되거나 로딩 화면이 더 오래 걸리거나 멈출 수 있습니다.\n\n게임 내에서 선호하는 핫키로 전환할 수 있습니다(기본값 F1). 핫키를 비활성화할 계획이라면 이 작업을 수행하는 것이 좋습니다.\n\n이 옵션에 대해 잘 모른다면 켜기를 권장드립니다.", "PptcToggleTooltip": "게임이 불러올 때마다 번역할 필요가 없도록 번역된 JIT 기능을 저장합니다.\n\n게임을 처음 부팅한 후 끊김 현상을 줄이고 부팅 시간을 크게 단축합니다.\n\n확실하지 않으면 켜 두세요.", "FsIntegrityToggleTooltip": "게임을 부팅할 때 손상된 파일을 확인하고 손상된 파일이 감지되면 로그에 해시 오류를 표시합니다.\n\n성능에 영향을 미치지 않으며 문제 해결에 도움이 됩니다.\n\n확실하지 않으면 켜 두세요.", "AudioBackendTooltip": "오디오를 렌더링하는 데 사용되는 백엔드를 변경합니다.\n\nSDL2가 선호되는 반면 OpenAL 및 사운드IO는 폴백으로 사용됩니다. 더미는 소리가 나지 않습니다.\n\n확실하지 않으면 SDL2로 설정하세요.", @@ -472,10 +472,10 @@ "GraphicsBackendThreadingTooltip": "두 번째 스레드에서 그래픽 백엔드 명령을 실행합니다.\n\n세이더 컴파일 속도를 높이고 끊김 현상을 줄이며 자체 멀티스레딩 지원 없이 GPU 드라이버의 성능을 향상시킵니다. 멀티스레딩이 있는 드라이버에서 성능이 약간 향상되었습니다.\n\n잘 모르겠으면 자동으로 설정하세요.", "GalThreadingTooltip": "두 번째 스레드에서 그래픽 백엔드 명령을 실행합니다.\n\n세이더 컴파일 속도를 높이고 끊김 현상을 줄이며 자체 멀티스레딩 지원 없이 GPU 드라이버의 성능을 향상시킵니다. 멀티스레딩이 있는 드라이버에서 성능이 약간 향상되었습니다.\n\n잘 모르겠으면 자동으로 설정하세요.", "ShaderCacheToggleTooltip": "후속 실행에서 끊김 현상을 줄이는 디스크 세이더 캐시를 저장합니다.\n\n확실하지 않으면 켜 두세요.", - "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", + "ResolutionScaleTooltip": "게임의 렌더링 해상도를 늘립니다.\n\n일부 게임에서는 해당 기능을 지원하지 않거나 해상도가 늘어났음에도 픽셀이 자글자글해 보일 수 있습니다; 이러한 게임들의 경우 사용자가 직접 안티 앨리어싱 기능을 끄는 Mod나 내부 렌더링 해상도를 증가시키는 Mod 등을 찾아보아야 합니다. 후자의 Mod를 사용 시에는 해당 옵션을 네이티브로 두시는 것이 좋습니다.\n\n이 옵션은 게임이 구동중일 때에도 아래 Apply 버튼을 눌러서 변경할 수 있습니다; 설정 창을 게임 창 옆에 두고 사용자가 선호하는 해상도를 실험하여 고를 수 있습니다.\n\n4x 설정은 어떤 셋업에서도 무리인 점을 유의하세요.", "ResolutionScaleEntryTooltip": "1.5와 같은 부동 소수점 분해능 스케일입니다. 비통합 척도는 문제나 충돌을 일으킬 가능성이 더 큽니다.", - "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", - "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", + "AnisotropyTooltip": "비등방성 필터링 레벨. 게임에서 요청한 값을 사용하려면 자동으로 설정하세요.", + "AspectRatioTooltip": "렌더러 창에 적용될 화면비.\n\n화면비를 변경하는 Mod를 사용할 때에만 이 옵션을 바꾸세요, 그렇지 않을 경우 그래픽이 늘어나 보일 수 있습니다.\n\n이 옵션에 대해 잘 모른다면 16:9로 설정하세요.", "ShaderDumpPathTooltip": "그래픽 셰이더 덤프 경로", "FileLogTooltip": "디스크의 로그 파일에 콘솔 로깅을 저장합니다. 성능에 영향을 미치지 않습니다.", "StubLogTooltip": "콘솔에 스텁 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", @@ -509,8 +509,8 @@ "EnableInternetAccessTooltip": "에뮬레이션된 응용프로그램이 인터넷에 연결되도록 허용합니다.\n\nLAN 모드가 있는 게임은 이 모드가 활성화되고 시스템이 동일한 접속 포인트에 연결된 경우 서로 연결할 수 있습니다. 여기에는 실제 콘솔도 포함됩니다.\n\n닌텐도 서버에 연결할 수 없습니다. 인터넷에 연결을 시도하는 특정 게임에서 충돌이 발생할 수 있습니다.\n\n확실하지 않으면 꺼두세요.", "GameListContextMenuManageCheatToolTip": "치트 관리", "GameListContextMenuManageCheat": "치트 관리", - "GameListContextMenuManageModToolTip": "Manage Mods", - "GameListContextMenuManageMod": "Manage Mods", + "GameListContextMenuManageModToolTip": "Mod 관리", + "GameListContextMenuManageMod": "Mod 관리", "ControllerSettingsStickRange": "범위 :", "DialogStopEmulationTitle": "Ryujinx - 에뮬레이션 중지", "DialogStopEmulationMessage": "에뮬레이션을 중지하겠습니까?", @@ -552,10 +552,10 @@ "SoftwareKeyboardModeNumeric": "'0~9' 또는 '.'만 가능", "SoftwareKeyboardModeAlphabet": "한중일 문자가 아닌 문자만 가능", "SoftwareKeyboardModeASCII": "ASCII 텍스트만 가능", - "ControllerAppletControllers": "Supported Controllers:", - "ControllerAppletPlayers": "Players:", - "ControllerAppletDescription": "Your current configuration is invalid. Open settings and reconfigure your inputs.", - "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.", + "ControllerAppletControllers": "지원하는 컨트롤러:", + "ControllerAppletPlayers": "플레이어:", + "ControllerAppletDescription": "현재 설정은 유효하지 않습니다. 설정을 열어 입력 장치를 다시 설정하세요.", + "ControllerAppletDocked": "독 모드가 설정되었습니다. 핸드헬드 컨트롤은 비활성화됩니다.", "UpdaterRenaming": "이전 파일 이름 바꾸는 중...", "UpdaterRenameFailed": "업데이터가 파일 이름을 바꿀 수 없음: {0}", "UpdaterAddingFiles": "새로운 파일을 추가하는 중...", @@ -593,7 +593,7 @@ "Writable": "쓰기 가능", "SelectDlcDialogTitle": "DLC 파일 선택", "SelectUpdateDialogTitle": "업데이트 파일 선택", - "SelectModDialogTitle": "Select mod directory", + "SelectModDialogTitle": "Mod 디렉터리 선택", "UserProfileWindowTitle": "사용자 프로파일 관리자", "CheatWindowTitle": "치트 관리자", "DlcWindowTitle": "{0} ({1})의 다운로드 가능한 콘텐츠 관리", @@ -616,9 +616,9 @@ "UserProfilesName": "이름 :", "UserProfilesUserId": "사용자 ID :", "SettingsTabGraphicsBackend": "그래픽 후단부", - "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", + "SettingsTabGraphicsBackendTooltip": "에뮬레이터에 사용될 그래픽 백엔드를 선택합니다.\n\nVulkan이 드라이버가 최신이기 때문에 모든 현대 그래픽 카드들에서 더 좋은 성능을 발휘합니다. 또한 Vulkan은 모든 벤더사의 GPU에서 더 빠른 쉐이더 컴파일을 지원하여 스터터링이 적습니다.\n\nOpenGL의 경우 오래된 Nvidia GPU나 오래된 AMD GPU(리눅스 한정), 혹은 VRAM이 적은 GPU에서 더 나은 성능을 발휘할 수는 있으나 쉐이더 컴파일로 인한 스터터링이 Vulkan보다 심할 수 있습니다.\n\n이 옵션에 대해 잘 모른다면 Vulkan으로 설정하세요. 사용하는 GPU가 최신 그래픽 드라이버에서도 Vulkan을 지원하지 않는다면 그 땐 OpenGL로 설정하세요.", "SettingsEnableTextureRecompression": "텍스처 재압축 활성화", - "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", + "SettingsEnableTextureRecompressionTooltip": "ASTC 텍스처를 압축하여 VRAM 사용량을 줄입니다.\n\n애스트럴 체인, 바요네타 3, 파이어 엠블렘 인게이지, 메트로이드 프라임 리마스터, 슈퍼 마리오브라더스 원더, 젤다의 전설: 티어스 오브 더 킹덤 등이 이러한 텍스처 포맷을 사용합니다.\n\nVRAM이 4GiB 이하인 그래픽 카드로 위와 같은 게임들을 구동할시 특정 지점에서 크래시가 발생할 수 있습니다.\n\n위에 서술된 게임들에서 VRAM이 부족한 경우에만 해당 옵션을 켜고, 그 외의 경우에는 끄기를 권장드립니다.", "SettingsTabGraphicsPreferredGpu": "선호하는 GPU", "SettingsTabGraphicsPreferredGpuTooltip": "Vulkan 그래픽 후단부와 함께 사용할 그래픽 카드를 선택하세요.\n\nOpenGL이 사용할 GPU에는 영향을 미치지 않습니다.\n\n확실하지 않은 경우 \"dGPU\" 플래그가 지정된 GPU로 설정하세요. 없는 경우, 그대로 두세요.", "SettingsAppRequiredRestartMessage": "Ryujinx 다시 시작 필요", @@ -644,12 +644,15 @@ "Recover": "복구", "UserProfilesRecoverHeading": "다음 계정에 대한 저장 발견", "UserProfilesRecoverEmptyList": "복구할 프로파일이 없습니다", - "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", + "GraphicsAATooltip": "게임 렌더에 안티 앨리어싱을 적용합니다.\n\nFXAA는 대부분의 이미지를 뿌옇게 만들지만, SMAA는 들쭉날쭉한 모서리 부분들을 찾아 부드럽게 만듭니다.\n\nFSR 스케일링 필터와 같이 사용하는 것은 권장하지 않습니다.\n\n이 옵션은 게임이 구동중일 때에도 아래 Apply 버튼을 눌러서 변경할 수 있습니다; 설정 창을 게임 창 옆에 두고 사용자가 선호하는 옵션을 실험하여 고를 수 있습니다.\n\n이 옵션에 대해 잘 모른다면 끄기를 권장드립니다.", "GraphicsAALabel": "안티 앨리어싱:", "GraphicsScalingFilterLabel": "스케일링 필터:", - "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterTooltip": "해상도 스케일에 사용될 스케일링 필터를 선택하세요.\n\nBilinear는 3D 게임에서 잘 작동하며 안전한 기본값입니다.\n\nNearest는 픽셀 아트 게임에 추천합니다.\n\nFSR 1.0은 그저 샤프닝 필터임으로, FXAA나 SMAA와 같이 사용하는 것은 권장하지 않습니다.\n\n이 옵션은 게임이 구동중일 때에도 아래 Apply 버튼을 눌러서 변경할 수 있습니다; 설정 창을 게임 창 옆에 두고 사용자가 선호하는 옵션을 실험하여 고를 수 있습니다.\n\n이 옵션에 대해 잘 모른다면 BILINEAR로 두세요.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "수준", - "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", + "GraphicsScalingFilterLevelTooltip": "FSR 1.0의 샤프닝 레벨을 설정하세요. 높을수록 더 또렷해집니다.", "SmaaLow": "SMAA 낮음", "SmaaMedium": "SMAA 중간", "SmaaHigh": "SMAA 높음", @@ -657,12 +660,14 @@ "UserEditorTitle": "사용자 수정", "UserEditorTitleCreate": "사용자 생성", "SettingsTabNetworkInterface": "네트워크 인터페이스:", - "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", + "NetworkInterfaceTooltip": "LAN/LDN 기능에 사용될 네트워크 인터페이스입니다.\n\nLAN 기능을 지원하는 게임에서 VPN이나 XLink Kai 등을 동시에 사용하면, 인터넷을 통해 동일 네트워크 연결인 것을 속일 수 있습니다.\n\n이 옵션에 대해 잘 모른다면 기본값으로 설정하세요.", "NetworkInterfaceDefault": "기본", "PackagingShaders": "셰이더 패키징 중", "AboutChangelogButton": "GitHub에서 변경 로그 보기", "AboutChangelogButtonTooltipMessage": "기본 브라우저에서 이 버전의 변경 로그를 열려면 클릭합니다.", "SettingsTabNetworkMultiplayer": "멀티 플레이어", "MultiplayerMode": "모드 :", - "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." + "MultiplayerModeTooltip": "LDN 멀티플레이어 모드를 변경합니다.\n\nLdnMitm은 로컬 무선/로컬 플레이 기능을 수정하여 LAN 모드에 있는 것처럼 만들어 로컬이나 동일한 네트워크 상에 있는 다른 Ryujinx 인스턴스나 커펌된 닌텐도 스위치 콘솔(ldn_mitm 모듈 설치 필요)과 연결할 수 있습니다.\n\n멀티플레이어 모드는 모든 플레이어들이 동일한 게임 버전을 요구합니다. 예를 들어 슈퍼 스매시브라더스 얼티밋 v13.0.1 사용자는 v13.0.0 사용자와 연결할 수 없습니다.\n\n해당 옵션에 대해 잘 모른다면 비활성화해두세요.", + "MultiplayerModeDisabled": "비활성화됨", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/pl_PL.json b/src/Ryujinx/Assets/Locales/pl_PL.json index 4647f4308d..94096dd7b4 100644 --- a/src/Ryujinx/Assets/Locales/pl_PL.json +++ b/src/Ryujinx/Assets/Locales/pl_PL.json @@ -341,7 +341,7 @@ "DialogThemeRestartMessage": "Motyw został zapisany. Aby zastosować motyw, konieczne jest ponowne uruchomienie.", "DialogThemeRestartSubMessage": "Czy chcesz uruchomić ponownie?", "DialogFirmwareInstallEmbeddedMessage": "Czy chcesz zainstalować firmware wbudowany w tę grę? (Firmware {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "Nie znaleziono zainstalowanego firmware'u, ale Ryujinx był w stanie zainstalować firmware {0} z dostarczonej gry.\n\nEmulator uruchomi się teraz.", + "DialogFirmwareInstallEmbeddedSuccessMessage": "No installed firmware was found but Ryujinx was able to install firmware {0} from the provided game.\nThe emulator will now start.", "DialogFirmwareNoFirmwareInstalledMessage": "Brak Zainstalowanego Firmware'u", "DialogFirmwareInstalledMessage": "Firmware {0} został zainstalowany", "DialogInstallFileTypesSuccessMessage": "Pomyślnie zainstalowano typy plików!", @@ -435,7 +435,7 @@ "DlcManagerEnableAllButton": "Włącz Wszystkie", "DlcManagerDisableAllButton": "Wyłącz Wszystkie", "ModManagerDeleteAllButton": "Usuń wszystko", - "MenuBarOptionsChangeLanguage": "Zmień Język", + "MenuBarOptionsChangeLanguage": "Zmień język", "MenuBarShowFileTypes": "Pokaż typy plików", "CommonSort": "Sortuj", "CommonShowNames": "Pokaż Nazwy", @@ -455,7 +455,7 @@ "DirectKeyboardTooltip": "Obsługa bezpośredniego dostępu do klawiatury (HID). Zapewnia dostęp gier do klawiatury jako urządzenia do wprowadzania tekstu.\n\nDziała tylko z grami, które natywnie wspierają użycie klawiatury w urządzeniu Switch hardware.\n\nPozostaw wyłączone w razie braku pewności.", "DirectMouseTooltip": "Obsługa bezpośredniego dostępu do myszy (HID). Zapewnia dostęp gier do myszy jako urządzenia wskazującego.\n\nDziała tylko z grami, które natywnie obsługują przyciski myszy w urządzeniu Switch, które są nieliczne i daleko między nimi.\n\nPo włączeniu funkcja ekranu dotykowego może nie działać.\n\nPozostaw wyłączone w razie wątpliwości.", "RegionTooltip": "Zmień Region Systemu", - "LanguageTooltip": "Zmień Język Systemu", + "LanguageTooltip": "Zmień język systemu", "TimezoneTooltip": "Zmień Strefę Czasową Systemu", "TimeTooltip": "Zmień czas systemowy", "VSyncToggleTooltip": "Synchronizacja pionowa emulowanej konsoli. Zasadniczo ogranicznik klatek dla większości gier; wyłączenie jej może spowodować, że gry będą działać z większą szybkością, ekrany wczytywania wydłużą się lub nawet utkną.\n\nMoże być przełączana w grze za pomocą preferowanego skrótu klawiszowego. Zalecamy to zrobić, jeśli planujesz ją wyłączyć.\n\nW razie wątpliwości pozostaw WŁĄCZONĄ.", @@ -523,7 +523,7 @@ "DialogUpdaterFlatpakNotSupportedMessage": "Zaktualizuj Ryujinx przez FlatHub.", "UpdaterDisabledWarningTitle": "Aktualizator Wyłączony!", "ControllerSettingsRotate90": "Obróć o 90° w Prawo", - "IconSize": "Rozmiar Ikon", + "IconSize": "Rozmiar ikon", "IconSizeTooltip": "Zmień rozmiar ikon gry", "MenuBarOptionsShowConsole": "Pokaż Konsolę", "ShaderCachePurgeError": "Błąd podczas czyszczenia cache shaderów w {0}: {1}", @@ -648,6 +648,9 @@ "GraphicsAALabel": "Antyaliasing:", "GraphicsScalingFilterLabel": "Filtr skalowania:", "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "Poziom", "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "SMAA Niskie", @@ -664,5 +667,7 @@ "AboutChangelogButtonTooltipMessage": "Kliknij, aby otworzyć listę zmian dla tej wersji w domyślnej przeglądarce.", "SettingsTabNetworkMultiplayer": "Gra Wieloosobowa", "MultiplayerMode": "Tryb:", - "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.", + "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/pt_BR.json b/src/Ryujinx/Assets/Locales/pt_BR.json index cd6a2fd1c8..fb48bd5937 100644 --- a/src/Ryujinx/Assets/Locales/pt_BR.json +++ b/src/Ryujinx/Assets/Locales/pt_BR.json @@ -341,7 +341,7 @@ "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.", + "DialogFirmwareInstallEmbeddedSuccessMessage": "No installed firmware was found but Ryujinx was able to install firmware {0} from the provided game.\nThe emulator will now start.", "DialogFirmwareNoFirmwareInstalledMessage": "Firmware não foi instalado", "DialogFirmwareInstalledMessage": "Firmware {0} foi instalado", "DialogInstallFileTypesSuccessMessage": "Tipos de arquivo instalados com sucesso!", @@ -648,6 +648,9 @@ "GraphicsAALabel": "Anti-serrilhado:", "GraphicsScalingFilterLabel": "Filtro de escala:", "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "Nível", "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "SMAA Baixo", @@ -664,5 +667,7 @@ "AboutChangelogButtonTooltipMessage": "Clique para abrir o relatório de alterações para esta versão no seu navegador padrão.", "SettingsTabNetworkMultiplayer": "Multiplayer", "MultiplayerMode": "Modo:", - "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.", + "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/ru_RU.json b/src/Ryujinx/Assets/Locales/ru_RU.json index e7c36f6ca4..284b8e2b4a 100644 --- a/src/Ryujinx/Assets/Locales/ru_RU.json +++ b/src/Ryujinx/Assets/Locales/ru_RU.json @@ -31,10 +31,10 @@ "MenuBarToolsInstallFileTypes": "Установить типы файлов", "MenuBarToolsUninstallFileTypes": "Удалить типы файлов", "MenuBarHelp": "_Помощь", - "MenuBarHelpCheckForUpdates": "Проверка обновлений", + "MenuBarHelpCheckForUpdates": "Проверить наличие обновлений", "MenuBarHelpAbout": "О программе", "MenuSearch": "Поиск...", - "GameListHeaderFavorite": "Избранные", + "GameListHeaderFavorite": "Избранное", "GameListHeaderIcon": "Значок", "GameListHeaderApplication": "Название", "GameListHeaderDeveloper": "Разработчик", @@ -45,23 +45,23 @@ "GameListHeaderFileSize": "Размер файла", "GameListHeaderPath": "Путь", "GameListContextMenuOpenUserSaveDirectory": "Открыть папку с сохранениями", - "GameListContextMenuOpenUserSaveDirectoryToolTip": "Открывает папку, содержащую пользовательские сохранения", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Открывает папку с пользовательскими сохранениями", "GameListContextMenuOpenDeviceSaveDirectory": "Открыть папку сохраненных устройств", "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Открывает папку, содержащую сохраненные устройства", "GameListContextMenuOpenBcatSaveDirectory": "Открыть папку сохраненных BCAT", - "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Открывает папку, содержащую сохраненные BCAT.", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Открывает папку, содержащую сохраненные BCAT", "GameListContextMenuManageTitleUpdates": "Управление обновлениями", - "GameListContextMenuManageTitleUpdatesToolTip": "Открывает окно управления обновлением заголовков", + "GameListContextMenuManageTitleUpdatesToolTip": "Открывает окно управления обновлениями приложения", "GameListContextMenuManageDlc": "Управление DLC", "GameListContextMenuManageDlcToolTip": "Открывает окно управления DLC", "GameListContextMenuCacheManagement": "Управление кэшем", - "GameListContextMenuCacheManagementPurgePptc": "Перестройка очереди PPTC", - "GameListContextMenuCacheManagementPurgePptcToolTip": "Запускает перестройку PPTC во время запуска следующей игры.", + "GameListContextMenuCacheManagementPurgePptc": "Перестроить очередь PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Запускает перестройку PPTC во время следующего запуска игры.", "GameListContextMenuCacheManagementPurgeShaderCache": "Очистить кэш шейдеров", "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Удаляет кеш шейдеров приложения", "GameListContextMenuCacheManagementOpenPptcDirectory": "Открыть папку PPTC", "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Открывает папку, содержащую PPTC кэш приложений и игр", - "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Открыть папку кэша шейдеров", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Открыть папку с кэшем шейдеров", "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Открывает папку, содержащую кэш шейдеров приложений и игр", "GameListContextMenuExtractData": "Извлечь данные", "GameListContextMenuExtractDataExeFS": "ExeFS", @@ -71,13 +71,13 @@ "GameListContextMenuExtractDataLogo": "Логотип", "GameListContextMenuExtractDataLogoToolTip": "Извлечение раздела с логотипом из текущих настроек приложения (включая обновления)", "GameListContextMenuCreateShortcut": "Создать ярлык приложения", - "GameListContextMenuCreateShortcutToolTip": "Создать ярлык на рабочем столе, с помощью которого можно запустить игру или приложение", - "GameListContextMenuCreateShortcutToolTipMacOS": "Создать ярлык игры или приложения в папке Программы macOS", + "GameListContextMenuCreateShortcutToolTip": "Создает ярлык на рабочем столе, с помощью которого можно запустить игру или приложение", + "GameListContextMenuCreateShortcutToolTipMacOS": "Создает ярлык игры или приложения в папке Программы macOS", "GameListContextMenuOpenModsDirectory": "Открыть папку с модами", "GameListContextMenuOpenModsDirectoryToolTip": "Открывает папку, содержащую моды для приложений и игр", "GameListContextMenuOpenSdModsDirectory": "Открыть папку с модами Atmosphere", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Открывает альтернативную папку Atmosphere SD-карты, которая содержит моды для приложений и игр. Полезно для модов, сделанных для реальной консоли.", - "StatusBarGamesLoaded": "{0}/{1} Игр загружено", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Открывает папку Atmosphere на альтернативной SD-карте, которая содержит моды для приложений и игр. Полезно для модов, сделанных для реальной консоли.", + "StatusBarGamesLoaded": "{0}/{1} игр загружено", "StatusBarSystemVersion": "Версия прошивки: {0}", "LinuxVmMaxMapCountDialogTitle": "Обнаружен низкий лимит разметки памяти", "LinuxVmMaxMapCountDialogTextPrimary": "Вы хотите увеличить значение vm.max_map_count до {0}", @@ -85,7 +85,7 @@ "LinuxVmMaxMapCountDialogButtonUntilRestart": "Да, до следующего перезапуска", "LinuxVmMaxMapCountDialogButtonPersistent": "Да, постоянно", "LinuxVmMaxMapCountWarningTextPrimary": "Максимальная разметка памяти меньше, чем рекомендуется.", - "LinuxVmMaxMapCountWarningTextSecondary": "Текущее значение vm.max_map_count ({0}) меньше, чем {1}. Некоторые игры могут попытаться создать большую разметку памяти, чем разрешено в данный момент. Ryujinx вылетит как только этот лимит будет превышен.\n\nВозможно, вы захотите вручную увеличить лимит или установить pkexec, что позволит Ryujinx помочь справиться с превышением лимита.", + "LinuxVmMaxMapCountWarningTextSecondary": "Текущее значение vm.max_map_count ({0}) меньше, чем {1}. Некоторые игры могут попытаться создать большую разметку памяти, чем разрешено в данный момент. Ryujinx вылетит как только этот лимит будет превышен.\n\nВозможно, вам потребуется вручную увеличить лимит или установить pkexec, что позволит Ryujinx помочь справиться с превышением лимита.", "Settings": "Параметры", "SettingsTabGeneral": "Интерфейс", "SettingsTabGeneralGeneral": "Общее", @@ -124,7 +124,7 @@ "SettingsTabSystemSystemLanguageTaiwanese": "Тайванский", "SettingsTabSystemSystemLanguageBritishEnglish": "Английский (Британия)", "SettingsTabSystemSystemLanguageCanadianFrench": "Французский (Канада)", - "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Испанский (Латиноамериканский)", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Испанский (Латинская Америка)", "SettingsTabSystemSystemLanguageSimplifiedChinese": "Китайский упрощённый", "SettingsTabSystemSystemLanguageTraditionalChinese": "Китайский традиционный", "SettingsTabSystemSystemTimeZone": "Часовой пояс прошивки:", @@ -151,7 +151,7 @@ "SettingsTabGraphicsAnisotropicFiltering8x": "8x", "SettingsTabGraphicsAnisotropicFiltering16x": "16x", "SettingsTabGraphicsResolutionScale": "Масштабирование:", - "SettingsTabGraphicsResolutionScaleCustom": "Пользовательский (не рекомендуется)", + "SettingsTabGraphicsResolutionScaleCustom": "Пользовательское (не рекомендуется)", "SettingsTabGraphicsResolutionScaleNative": "Нативное (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", @@ -207,7 +207,7 @@ "ControllerSettingsDeviceDisabled": "Отключить", "ControllerSettingsControllerType": "Тип контроллера", "ControllerSettingsControllerTypeHandheld": "Портативный", - "ControllerSettingsControllerTypeProController": "Pro Контроллер", + "ControllerSettingsControllerTypeProController": "Pro Controller", "ControllerSettingsControllerTypeJoyConPair": "JoyCon (пара)", "ControllerSettingsControllerTypeJoyConLeft": "JoyCon (левый)", "ControllerSettingsControllerTypeJoyConRight": "JoyCon (правый)", @@ -268,35 +268,35 @@ "ControllerSettingsClose": "Закрыть", "UserProfilesSelectedUserProfile": "Выбранный пользовательский профиль:", "UserProfilesSaveProfileName": "Сохранить пользовательский профиль", - "UserProfilesChangeProfileImage": "Изменить изображение профиля", + "UserProfilesChangeProfileImage": "Изменить аватар", "UserProfilesAvailableUserProfiles": "Доступные профили пользователей:", "UserProfilesAddNewProfile": "Добавить новый профиль", "UserProfilesDelete": "Удалить", "UserProfilesClose": "Закрыть", "ProfileNameSelectionWatermark": "Выберите никнейм", "ProfileImageSelectionTitle": "Выбор изображения профиля", - "ProfileImageSelectionHeader": "Выберите изображение профиля", - "ProfileImageSelectionNote": "Вы можете импортировать собственное изображение профиля или выбрать аватар из системной прошивки.", + "ProfileImageSelectionHeader": "Выберите аватар", + "ProfileImageSelectionNote": "Вы можете импортировать собственное изображение или выбрать аватар из системной прошивки.", "ProfileImageSelectionImportImage": "Импорт изображения", - "ProfileImageSelectionSelectAvatar": "Выстроенные аватары", + "ProfileImageSelectionSelectAvatar": "Встроенные аватары", "InputDialogTitle": "Диалоговое окно ввода", "InputDialogOk": "ОК", "InputDialogCancel": "Отмена", "InputDialogAddNewProfileTitle": "Выберите имя профиля", "InputDialogAddNewProfileHeader": "Пожалуйста, введите имя профиля", "InputDialogAddNewProfileSubtext": "(Максимальная длина: {0})", - "AvatarChoose": "Выбор аватара", + "AvatarChoose": "Выбрать аватар", "AvatarSetBackgroundColor": "Установить цвет фона", "AvatarClose": "Закрыть", "ControllerSettingsLoadProfileToolTip": "Загрузить профиль", - "ControllerSettingsAddProfileToolTip": "Добавить профил", + "ControllerSettingsAddProfileToolTip": "Добавить профиль", "ControllerSettingsRemoveProfileToolTip": "Удалить профиль", "ControllerSettingsSaveProfileToolTip": "Сохранить профиль", "MenuBarFileToolsTakeScreenshot": "Сделать снимок экрана", - "MenuBarFileToolsHideUi": "Скрыть UI", + "MenuBarFileToolsHideUi": "Скрыть интерфейс", "GameListContextMenuRunApplication": "Запуск приложения", "GameListContextMenuToggleFavorite": "Добавить в избранное", - "GameListContextMenuToggleFavoriteToolTip": "Помечает игру звездочкой как избранную", + "GameListContextMenuToggleFavoriteToolTip": "Добавляет игру в избранное и помечает звездочкой", "SettingsTabGeneralTheme": "Тема:", "SettingsTabGeneralThemeDark": "Темная", "SettingsTabGeneralThemeLight": "Светлая", @@ -304,7 +304,7 @@ "ControllerSettingsRumble": "Вибрация", "ControllerSettingsRumbleStrongMultiplier": "Множитель сильной вибрации", "ControllerSettingsRumbleWeakMultiplier": "Множитель слабой вибрации", - "DialogMessageSaveNotAvailableMessage": "Нет сохраненных данных для {0} [{1:x16}]", + "DialogMessageSaveNotAvailableMessage": "Нет сохранений для {0} [{1:x16}]", "DialogMessageSaveNotAvailableCreateSaveMessage": "Создать сохранение для этой игры?", "DialogConfirmationTitle": "Ryujinx - Подтверждение", "DialogUpdaterTitle": "Ryujinx - Обновление", @@ -313,52 +313,52 @@ "DialogExitTitle": "Ryujinx - Выход", "DialogErrorMessage": "Ryujinx обнаружил ошибку", "DialogExitMessage": "Вы уверены, что хотите закрыть Ryujinx?", - "DialogExitSubMessage": "Все несохраненные данные будут потеряны!", + "DialogExitSubMessage": "Все несохраненные данные будут потеряны", "DialogMessageCreateSaveErrorMessage": "Произошла ошибка при создании указанных данных сохранения: {0}", "DialogMessageFindSaveErrorMessage": "Произошла ошибка при поиске указанных данных сохранения: {0}", "FolderDialogExtractTitle": "Выберите папку для извлечения", - "DialogNcaExtractionMessage": "Извлечение {0} раздел от {1}...", - "DialogNcaExtractionTitle": "Ryujinx - Экстрактор разделов NCA", + "DialogNcaExtractionMessage": "Извлечение {0} раздела из {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Извлечение разделов NCA", "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Ошибка извлечения. Основной NCA не присутствовал в выбранном файле.", "DialogNcaExtractionCheckLogErrorMessage": "Ошибка извлечения. Прочтите файл журнала для получения дополнительной информации.", "DialogNcaExtractionSuccessMessage": "Извлечение завершено успешно.", "DialogUpdaterConvertFailedMessage": "Не удалось преобразовать текущую версию Ryujinx.", - "DialogUpdaterCancelUpdateMessage": "Отмена обновления!", - "DialogUpdaterAlreadyOnLatestVersionMessage": "Вы уже используете самую последнюю версию Ryujinx!", + "DialogUpdaterCancelUpdateMessage": "Отмена обновления...", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Вы уже используете самую последнюю версию Ryujinx", "DialogUpdaterFailedToGetVersionMessage": "Произошла ошибка при попытке получить информацию о выпуске от GitHub Release. Это может быть вызвано тем, что в данный момент в GitHub Actions компилируется новый релиз. Повторите попытку позже.", "DialogUpdaterConvertFailedGithubMessage": "Не удалось преобразовать полученную версию Ryujinx из Github Release.", "DialogUpdaterDownloadingMessage": "Загрузка обновления...", "DialogUpdaterExtractionMessage": "Извлечение обновления...", "DialogUpdaterRenamingMessage": "Переименование обновления...", "DialogUpdaterAddingFilesMessage": "Добавление нового обновления...", - "DialogUpdaterCompleteMessage": "Обновление завершено!", + "DialogUpdaterCompleteMessage": "Обновление завершено", "DialogUpdaterRestartMessage": "Вы хотите перезапустить Ryujinx сейчас?", - "DialogUpdaterNoInternetMessage": "Вы не подключены к интернету!", - "DialogUpdaterNoInternetSubMessage": "Убедитесь, что у вас есть работающее подключение к интернету!", - "DialogUpdaterDirtyBuildMessage": "Вы не можете обновить Dirty Build!", + "DialogUpdaterNoInternetMessage": "Вы не подключены к интернету", + "DialogUpdaterNoInternetSubMessage": "Убедитесь, что у вас работает подключение к интернету", + "DialogUpdaterDirtyBuildMessage": "Вы не можете обновлять Dirty Build", "DialogUpdaterDirtyBuildSubMessage": "Загрузите Ryujinx по адресу https://ryujinx.org/ если вам нужна поддерживаемая версия.", "DialogRestartRequiredMessage": "Требуется перезагрузка", "DialogThemeRestartMessage": "Тема сохранена. Для применения темы требуется перезапуск.", "DialogThemeRestartSubMessage": "Вы хотите перезапустить?", "DialogFirmwareInstallEmbeddedMessage": "Хотите установить прошивку, встроенную в эту игру? (Прошивка {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "Установленная прошивка не найдена, но Ryujinx удалось установить прошивку {0} из предоставленной игры.\nТеперь эмулятор запустится.", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Установленная прошивка не была найдена, но Ryujinx удалось установить прошивку {0} из предоставленной игры.\nТеперь эмулятор запустится.", "DialogFirmwareNoFirmwareInstalledMessage": "Прошивка не установлена", "DialogFirmwareInstalledMessage": "Прошивка {0} была установлена", - "DialogInstallFileTypesSuccessMessage": "Успешно установлены типы файлов!", + "DialogInstallFileTypesSuccessMessage": "Типы файлов успешно установлены", "DialogInstallFileTypesErrorMessage": "Не удалось установить типы файлов.", - "DialogUninstallFileTypesSuccessMessage": "Успешно удалены типы файлов!", + "DialogUninstallFileTypesSuccessMessage": "Типы файлов успешно удалены", "DialogUninstallFileTypesErrorMessage": "Не удалось удалить типы файлов.", "DialogOpenSettingsWindowLabel": "Открыть окно параметров", "DialogControllerAppletTitle": "Апплет контроллера", - "DialogMessageDialogErrorExceptionMessage": "Ошибка отображения диалогового окна сообщений: {0}", + "DialogMessageDialogErrorExceptionMessage": "Ошибка отображения сообщения: {0}", "DialogSoftwareKeyboardErrorExceptionMessage": "Ошибка отображения программной клавиатуры: {0}", "DialogErrorAppletErrorExceptionMessage": "Ошибка отображения диалогового окна ErrorApplet: {0}", "DialogUserErrorDialogMessage": "{0}: {1}", "DialogUserErrorDialogInfoMessage": "\nДля получения дополнительной информации о том, как исправить эту ошибку, следуйте нашему Руководству по установке.", - "DialogUserErrorDialogTitle": "Ошибка Ryujinx! ({0})", + "DialogUserErrorDialogTitle": "Ошибка Ryujinx ({0})", "DialogAmiiboApiTitle": "Amiibo API", "DialogAmiiboApiFailFetchMessage": "Произошла ошибка при получении информации из API.", - "DialogAmiiboApiConnectErrorMessage": "Не удалось подключиться к серверу Amiibo API. Служба может быть недоступна, или вам может потребоваться проверить, подключено ли ваше интернет-соединение к сети.", + "DialogAmiiboApiConnectErrorMessage": "Не удалось подключиться к серверу Amiibo API. Служба может быть недоступна или вам может потребоваться проверить ваше интернет-соединение.", "DialogProfileInvalidProfileErrorMessage": "Профиль {0} несовместим с текущей системой конфигурации ввода.", "DialogProfileDefaultProfileOverwriteErrorMessage": "Профиль по умолчанию не может быть перезаписан", "DialogProfileDeleteProfileTitle": "Удаление профиля", @@ -369,8 +369,8 @@ "DialogShaderDeletionMessage": "Вы собираетесь удалить кэш шейдеров для:\n\n{0}\n\nВы уверены, что хотите продолжить?", "DialogShaderDeletionErrorMessage": "Ошибка очистки кэша шейдеров в {0}: {1}", "DialogRyujinxErrorMessage": "Ryujinx обнаружил ошибку", - "DialogInvalidTitleIdErrorMessage": "Ошибка пользовательского интерфейса: выбранная игра не имеет действительного идентификатора названия.", - "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Действительная системная прошивка не найдена в {0}.", + "DialogInvalidTitleIdErrorMessage": "Ошибка пользовательского интерфейса: выбранная игра не имеет действительного ID.", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Валидная системная прошивка не найдена в {0}.", "DialogFirmwareInstallerFirmwareInstallTitle": "Установить прошивку {0}", "DialogFirmwareInstallerFirmwareInstallMessage": "Будет установлена версия прошивки {0}.", "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nЭто заменит текущую версию прошивки {0}.", @@ -382,33 +382,33 @@ "DialogUserProfileUnsavedChangesTitle": "Внимание - Несохраненные изменения", "DialogUserProfileUnsavedChangesMessage": "Вы внесли изменения в этот профиль пользователя которые не были сохранены.", "DialogUserProfileUnsavedChangesSubMessage": "Вы хотите отменить изменения?", - "DialogControllerSettingsModifiedConfirmMessage": "Текущие настройки контроллера обновлены.", - "DialogControllerSettingsModifiedConfirmSubMessage": "Вы хотите сохранить?", + "DialogControllerSettingsModifiedConfirmMessage": "Текущие настройки управления обновлены.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Сохранить?", "DialogLoadFileErrorMessage": "{0}. Файл с ошибкой: {1}", "DialogModAlreadyExistsMessage": "Мод уже существует", "DialogModInvalidMessage": "Выбранная папка не содержит модов", - "DialogModDeleteNoParentMessage": "Невозможно удалить: не удалось найти папку мода \"{0}\"!", - "DialogDlcNoDlcErrorMessage": "Указанный файл не содержит DLC для выбранной игры!", + "DialogModDeleteNoParentMessage": "Невозможно удалить: не удалось найти папку мода \"{0}\"", + "DialogDlcNoDlcErrorMessage": "Указанный файл не содержит DLC для выбранной игры", "DialogPerformanceCheckLoggingEnabledMessage": "У вас включено ведение журнала отладки, предназначенное только для разработчиков.", - "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Для оптимальной производительности рекомендуется отключить ведение журнала отладки. Вы хотите отключить ведение журнала отладки сейчас?", - "DialogPerformanceCheckShaderDumpEnabledMessage": "У вас включен сброс шейдеров, который предназначен только для разработчиков.", - "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Для оптимальной производительности рекомендуется отключить сброс шейдеров. Вы хотите отключить сброс шейдеров сейчас?", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Для оптимальной производительности рекомендуется отключить ведение журнала отладки. Хотите отключить ведение журнала отладки?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "У вас включен дамп шейдеров, который предназначен только для разработчиков.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Для оптимальной производительности рекомендуется отключить дамп шейдеров. Хотите отключить дамп шейдеров?", "DialogLoadAppGameAlreadyLoadedMessage": "Игра уже загружена", "DialogLoadAppGameAlreadyLoadedSubMessage": "Пожалуйста, остановите эмуляцию или закройте эмулятор перед запуском другой игры.", - "DialogUpdateAddUpdateErrorMessage": "Указанный файл не содержит обновления для выбранного заголовка!", + "DialogUpdateAddUpdateErrorMessage": "Указанный файл не содержит обновлений для выбранного приложения", "DialogSettingsBackendThreadingWarningTitle": "Предупреждение: многопоточность в бэкенде", - "DialogSettingsBackendThreadingWarningMessage": "Ryujinx необходимо перезапустить после изменения этой опции, чтобы она полностью применилась. В зависимости от вашей платформы вам может потребоваться вручную отключить собственную многопоточность вашего драйвера при использовании Ryujinx.", + "DialogSettingsBackendThreadingWarningMessage": "Для применения этой настройки необходимо перезапустить Ryujinx. В зависимости от используемой вами операционной системы вам может потребоваться вручную отключить многопоточность драйвера при использовании Ryujinx.", "DialogModManagerDeletionWarningMessage": "Вы сейчас удалите мод: {0}\n\nВы уверены, что хотите продолжить?", - "DialogModManagerDeletionAllWarningMessage": "Вы сейчас удалите все выбранные файлы для этой игры.\n\nВы уверены, что хотите продолжить?", + "DialogModManagerDeletionAllWarningMessage": "Вы сейчас удалите все выбранные моды для этой игры.\n\nВы уверены, что хотите продолжить?", "SettingsTabGraphicsFeaturesOptions": "Функции & Улучшения", "SettingsTabGraphicsBackendMultithreading": "Многопоточность графического бэкенда:", "CommonAuto": "Автоматически", - "CommonOff": "Выключен", - "CommonOn": "Включен", + "CommonOff": "Выключено", + "CommonOn": "Включено", "InputDialogYes": "Да", "InputDialogNo": "Нет", "DialogProfileInvalidProfileNameErrorMessage": "Имя файла содержит недопустимые символы. Пожалуйста, попробуйте еще раз.", - "MenuBarOptionsPauseEmulation": "Пауза", + "MenuBarOptionsPauseEmulation": "Пауза эмуляции", "MenuBarOptionsResumeEmulation": "Продолжить", "AboutUrlTooltipMessage": "Нажмите, чтобы открыть веб-сайт Ryujinx", "AboutDisclaimerMessage": "Ryujinx никоим образом не связан ни с Nintendo™, ни с кем-либо из ее партнеров.", @@ -427,8 +427,8 @@ "AmiiboScanButtonLabel": "Сканировать", "AmiiboOptionsShowAllLabel": "Показать все Amiibo", "AmiiboOptionsUsRandomTagLabel": "Хак: Использовать случайный тег Uuid", - "DlcManagerTableHeadingEnabledLabel": "Велючено", - "DlcManagerTableHeadingTitleIdLabel": "Идентификатор заголовка", + "DlcManagerTableHeadingEnabledLabel": "Включено", + "DlcManagerTableHeadingTitleIdLabel": "ID приложения", "DlcManagerTableHeadingContainerPathLabel": "Путь к контейнеру", "DlcManagerTableHeadingFullPathLabel": "Полный путь", "DlcManagerRemoveAllButton": "Удалить все", @@ -439,17 +439,17 @@ "MenuBarShowFileTypes": "Показывать форматы файлов", "CommonSort": "Сортировка", "CommonShowNames": "Показывать названия", - "CommonFavorite": "Избранные", + "CommonFavorite": "Избранное", "OrderAscending": "По возрастанию", "OrderDescending": "По убыванию", "SettingsTabGraphicsFeatures": "Функции", "ErrorWindowTitle": "Окно ошибки", "ToggleDiscordTooltip": "Включает или отключает отображение в Discord статуса \"Сейчас играет\"", "AddGameDirBoxTooltip": "Введите папку игры для добавления в список", - "AddGameDirTooltip": "AДобавить папку с игрой в список", + "AddGameDirTooltip": "Добавить папку с играми в список", "RemoveGameDirTooltip": "Удалить выбранную папку игры", - "CustomThemeCheckTooltip": "Включить или отключить пользовательские темы в графическом интерфейсе", - "CustomThemePathTooltip": "Путь к пользовательской теме интерфейса", + "CustomThemeCheckTooltip": "Включить или отключить пользовательские темы", + "CustomThemePathTooltip": "Путь к пользовательской теме для интерфейса", "CustomThemeBrowseTooltip": "Просмотр пользовательской темы интерфейса", "DockModeToggleTooltip": "\"Стационарный\" режим запускает эмулятор, как если бы Nintendo Switch находилась в доке, что улучшает графику и разрешение в большинстве игр. И наоборот, при отключении этого режима эмулятор будет запускать игры в \"Портативном\" режиме, снижая качество графики.\n\nНастройте управление для Игрока 1 если планируете использовать в \"Стационарном\" режиме; настройте портативное управление если планируете использовать эмулятор в \"Портативном\" режиме.\n\nОставьте включенным если не уверены.", "DirectKeyboardTooltip": "Поддержка прямого ввода с клавиатуры (HID). Предоставляет игре прямой доступ к клавиатуре в качестве устройства ввода текста.\nРаботает только с играми, которые изначально поддерживают использование клавиатуры с Switch.\nРекомендуется оставить выключенным.", @@ -459,24 +459,24 @@ "TimezoneTooltip": "Изменение часового пояса прошивки", "TimeTooltip": "Изменение системного времени", "VSyncToggleTooltip": "Эмуляция вертикальной синхронизации консоли, которая ограничивает количество кадров в секунду в большинстве игр; отключение может привести к тому, что игры будут запущены с более высокой частотой кадров, но загрузка игры может занять больше времени, либо игра не запустится вообще.\n\nМожно включать и выключать эту настройку непосредственно в игре с помощью горячих клавиш (F1 по умолчанию). Если планируете отключить вертикальную синхронизацию, рекомендуем настроить горячие клавиши.\n\nРекомендуется оставить включенным.", - "PptcToggleTooltip": "Сохранение преобразованных JIT-функций таким образом, чтобы их не нужно преобразовывать по новой каждый раз при загрузке игры.\n\nУменьшает статтеры и значительно ускоряет последующую загрузку игр.\n\nРекомендуется оставить включенным.", + "PptcToggleTooltip": "Сохранение преобразованных JIT-функций для того, чтобы не преобразовывать их по новой каждый раз при запуске игры.\n\nУменьшает статтеры и значительно ускоряет последующую загрузку игр.\n\nРекомендуется оставить включенным.", "FsIntegrityToggleTooltip": "Проверяет поврежденные файлы при загрузке игры и если поврежденные файлы обнаружены, отображает ошибку о поврежденном хэше в журнале.\n\nНе влияет на производительность и необходим для помощи в устранении неполадок.\n\nРекомендуется оставить включенным.", - "AudioBackendTooltip": "Изменяет используемый аудио-бэкенд для рендера звука.\n\nSDL2 является предпочтительным, в то время как OpenAL и SoundIO используются в качестве резервных. При выборе \"Заглушки\" звук будет отсутствовать.\n\nРекомендуется использование SDL2.", + "AudioBackendTooltip": "Изменяет используемый аудио-бэкенд для рендера звука.\n\nSDL2 является предпочтительным, в то время как OpenAL и SoundIO используются в качестве резервных. \n\nРекомендуется использование SDL2.", "MemoryManagerTooltip": "Изменение разметки и доступа к гостевой памяти. Значительно влияет на производительность процессора.\n\nРекомендуется оставить \"Хост не установлен\"", "MemoryManagerSoftwareTooltip": "Использует таблицу страниц для преобразования адресов. Самая высокая точность, но самая низкая производительность.", "MemoryManagerHostTooltip": "Прямая разметка памяти в адресном пространстве хоста. Значительно более быстрая JIT-компиляция и запуск.", "MemoryManagerUnsafeTooltip": "Производит прямую разметку памяти, но не маскирует адрес в гостевом адресном пространстве перед получением доступа. Быстро, но небезопасно. Гостевое приложение может получить доступ к памяти из Ryujinx, поэтому в этом режиме рекомендуется запускать только те программы, которым вы доверяете.", "UseHypervisorTooltip": "Использует Hypervisor вместо JIT. Значительно увеличивает производительность, но может работать нестабильно.", "DRamTooltip": "Использует альтернативный макет MemoryMode для имитации использования Nintendo Switch в режиме разработчика.\n\nПолезно только для пакетов текстур с высоким разрешением или модов добавляющих разрешение 4К. Не улучшает производительность.\n\nРекомендуется оставить выключенным.", - "IgnoreMissingServicesTooltip": "Игнорирует нереализованные сервисы Horizon OS. Эта настройка поможет избежать вылеты при запуске определенных игр.\n\nРекомендуется оставить выключенным.", + "IgnoreMissingServicesTooltip": "Игнорирует нереализованные сервисы Horizon в новых прошивках. Эта настройка поможет избежать вылеты при запуске определенных игр.\n\nРекомендуется оставить выключенным.", "GraphicsBackendThreadingTooltip": "Выполняет команды графического бэкенда на втором потоке.\n\nУскоряет компиляцию шейдеров, уменьшает статтеры и повышает производительность на драйверах GPU без поддержки многопоточности. Производительность на драйверах с многопоточностью немного выше.\n\nРекомендуется оставить Автоматически.", "GalThreadingTooltip": "Выполняет команды графического бэкенда на втором потоке.\n\nУскоряет компиляцию шейдеров, уменьшает статтеры и повышает производительность на драйверах GPU без поддержки многопоточности. Производительность на драйверах с многопоточностью немного выше.\n\nРекомендуется оставить в Авто.", - "ShaderCacheToggleTooltip": "Сохраняет кэш шейдеров на диске, который уменьшает статтеры при последующих запусках.\n\nРекомендуется оставить включенным.", - "ResolutionScaleTooltip": "Увеличивает разрешение рендера игры.\n\nНекоторые игры могут не работать с этой настройкой и выглядеть смазано даже когда разрешение увеличено; Для этих игр вам может потребоваться найти моды, которые убирают сглаживание или увеличивают разрешение рендеринга этих игр. Для использования последнего, вам нужно будет выбрать опцию \"Нативное\".\n\nЭта опция может быть изменена во время игры по нажатию кнопки \"Применить\" ниже; Вы можете просто переместить окно настроек в сторону и поэкспериментировать, пока не подберете подходящие настройки для конкретной игры.\n\nИмейте в виду, что \"4x\" является излишеством.", + "ShaderCacheToggleTooltip": "Сохраняет кэш шейдеров на диске, для уменьшения статтеров при последующих запусках.\n\nРекомендуется оставить включенным.", + "ResolutionScaleTooltip": "Увеличивает разрешение рендера игры.\n\nНекоторые игры могут не работать с этой настройкой и выглядеть смазано даже когда разрешение увеличено; Для таких игр вам может потребоваться установить моды, которые убирают сглаживание или увеличивают разрешение рендеринга. Для использования последнего, вам нужно будет выбрать опцию \"Нативное\".\n\nЭта опция может быть изменена во время игры по нажатию кнопки \"Применить\" ниже; Вы можете просто переместить окно настроек в сторону и поэкспериментировать, пока не подберете подходящие настройки для конкретной игры.\n\nИмейте в виду, что \"4x\" является излишеством.", "ResolutionScaleEntryTooltip": "Масштабирование разрешения с плавающей запятой, например 1,5. Неинтегральное масштабирование с большой вероятностью вызовет сбои в работе.", - "AnisotropyTooltip": "Уровень анизотропной фильтрации. Установите значение «Авто», чтобы использовать значение в игре по умолчанию.", - "AspectRatioTooltip": "Соотношение сторон применимое к окну рендерера.\n\nИзмените эту настройку только если вы используете мод для соотношения сторон, иначе изображение будет растянуто.\n\nРекомендуется настройка 16:9.", - "ShaderDumpPathTooltip": "Путь дампа графических шейдеров", + "AnisotropyTooltip": "Уровень анизотропной фильтрации. Установите значение «Авто», чтобы использовать в игре значение по умолчанию игре.", + "AspectRatioTooltip": "Соотношение сторон окна рендерера.\n\nИзмените эту настройку только если вы используете мод для соотношения сторон, иначе изображение будет растянуто.\n\nРекомендуется настройка 16:9.", + "ShaderDumpPathTooltip": "Путь с дампами графических шейдеров", "FileLogTooltip": "Включает или отключает ведение журнала в файл на диске. Не влияет на производительность.", "StubLogTooltip": "Включает ведение журнала-заглушки. Не влияет на производительность.", "InfoLogTooltip": "Включает вывод сообщений информационного журнала в консоль. Не влияет на производительность.", @@ -484,15 +484,15 @@ "ErrorLogTooltip": "Включает вывод сообщений журнала ошибок. Не влияет на производительность.", "TraceLogTooltip": "Выводит сообщения журнала трассировки в консоли. Не влияет на производительность.", "GuestLogTooltip": "Включает вывод сообщений гостевого журнала. Не влияет на производительность.", - "FileAccessLogTooltip": "Включает вывод сообщений журнала доступа к файлам", + "FileAccessLogTooltip": "Включает вывод сообщений журнала доступа к файлам.", "FSAccessLogModeTooltip": "Включает вывод журнала доступа к файловой системе. Возможные режимы: 0-3", "DeveloperOptionTooltip": "Используйте с осторожностью", "OpenGlLogLevel": "Требует включения соответствующих уровней ведения журнала", "DebugLogTooltip": "Выводит журнал сообщений отладки в консоли.\n\nИспользуйте только в случае просьбы разработчика, так как включение этой функции затруднит чтение журналов и ухудшит работу эмулятора.", "LoadApplicationFileTooltip": "Открыть файловый менеджер для выбора файла, совместимого с Nintendo Switch.", "LoadApplicationFolderTooltip": "Открыть файловый менеджер для выбора распакованного приложения, совместимого с Nintendo Switch.", - "OpenRyujinxFolderTooltip": "Открывает папку файловой системы Ryujinx. ", - "OpenRyujinxLogsTooltip": "Открывает папку, в которую записываются логи", + "OpenRyujinxFolderTooltip": "Открывает папку с файлами Ryujinx. ", + "OpenRyujinxLogsTooltip": "Открывает папку в которую записываются логи", "ExitTooltip": "Выйти из Ryujinx", "OpenSettingsTooltip": "Открыть окно параметров", "OpenProfileManagerTooltip": "Открыть менеджер учетных записей", @@ -521,10 +521,10 @@ "SettingsTabCpuCache": "Кэш ЦП", "SettingsTabCpuMemory": "Память ЦП", "DialogUpdaterFlatpakNotSupportedMessage": "Пожалуйста, обновите Ryujinx через FlatHub.", - "UpdaterDisabledWarningTitle": "Обновление выключено!", + "UpdaterDisabledWarningTitle": "Средство обновления отключено", "ControllerSettingsRotate90": "Повернуть на 90° по часовой стрелке", "IconSize": "Размер обложек", - "IconSizeTooltip": "Изменить размер игровых иконок", + "IconSizeTooltip": "Изменить размер обложек", "MenuBarOptionsShowConsole": "Показать консоль", "ShaderCachePurgeError": "Ошибка очистки кэша шейдеров в {0}: {1}", "UserErrorNoKeys": "Ключи не найдены", @@ -536,9 +536,9 @@ "UserErrorNoKeysDescription": "Ryujinx не удалось найти ваш 'prod.keys' файл", "UserErrorNoFirmwareDescription": "Ryujinx не удалось найти ни одной установленной прошивки", "UserErrorFirmwareParsingFailedDescription": "Ryujinx не удалось распаковать выбранную прошивку. Обычно это вызвано устаревшими ключами.", - "UserErrorApplicationNotFoundDescription": "Ryujinx не удалось найти действительное приложение по указанному пути.", - "UserErrorUnknownDescription": "Произошла неизвестная ошибка!", - "UserErrorUndefinedDescription": "Произошла неизвестная ошибка! Такого не должно происходить. Пожалуйста, свяжитесь с разработчиками!", + "UserErrorApplicationNotFoundDescription": "Ryujinx не удалось найти валидное приложение по указанному пути.", + "UserErrorUnknownDescription": "Произошла неизвестная ошибка", + "UserErrorUndefinedDescription": "Произошла неизвестная ошибка. Этого не должно происходить, пожалуйста, свяжитесь с разработчиками.", "OpenSetupGuideMessage": "Открыть руководство по установке", "NoUpdate": "Без обновлений", "TitleUpdateVersionLabel": "Version {0} - {1}", @@ -564,7 +564,7 @@ "Game": "Игра", "Docked": "Стационарный режим", "Handheld": "Портативный режим", - "ConnectionError": "Ошибка соединения!", + "ConnectionError": "Ошибка соединения", "AboutPageDeveloperListMore": "{0} и другие...", "ApiError": "Ошибка API.", "LoadingHeading": "Загрузка {0}", @@ -577,10 +577,10 @@ "RyujinxUpdater": "Ryujinx - Обновление", "SettingsTabHotkeys": "Горячие клавиши", "SettingsTabHotkeysHotkeys": "Горячие клавиши", - "SettingsTabHotkeysToggleVsyncHotkey": "Вкл./выкл. VSync:", + "SettingsTabHotkeysToggleVsyncHotkey": "Вертикальная синхронизация:", "SettingsTabHotkeysScreenshotHotkey": "Скриншот:", - "SettingsTabHotkeysShowUiHotkey": "Показать UI:", - "SettingsTabHotkeysPauseHotkey": "Пауза:", + "SettingsTabHotkeysShowUiHotkey": "Показать интерфейс:", + "SettingsTabHotkeysPauseHotkey": "Пауза эмуляции:", "SettingsTabHotkeysToggleMuteHotkey": "Выключить звук:", "ControllerMotionTitle": "Настройки управления движением", "ControllerRumbleTitle": "Настройки вибрации", @@ -592,7 +592,7 @@ "Usage": "Применение", "Writable": "Доступно для записи", "SelectDlcDialogTitle": "Выберите файлы DLC", - "SelectUpdateDialogTitle": "Выберите файлы обновления", + "SelectUpdateDialogTitle": "Выберите файлы обновлений", "SelectModDialogTitle": "Выбрать папку с модами", "UserProfileWindowTitle": "Менеджер учетных записей", "CheatWindowTitle": "Менеджер читов", @@ -601,26 +601,26 @@ "CheatWindowHeading": "Доступные читы для {0} [{1}]", "BuildId": "ID версии:", "DlcWindowHeading": "{0} DLC", - "ModWindowHeading": "Мод(ы) {0} ", + "ModWindowHeading": "Моды для {0} ", "UserProfilesEditProfile": "Изменить выбранные", "Cancel": "Отмена", "Save": "Сохранить", "Discard": "Отменить", "Paused": "Приостановлено", - "UserProfilesSetProfileImage": "Установить изображение профиля", + "UserProfilesSetProfileImage": "Установить аватар профиля", "UserProfileEmptyNameError": "Имя обязательно", - "UserProfileNoImageError": "Изображение профиля должно быть установлено", - "GameUpdateWindowHeading": "Обновление доступно для {0} ({1})", + "UserProfileNoImageError": "Необходимо установить аватар", + "GameUpdateWindowHeading": "Доступные обновления для {0} ({1})", "SettingsTabHotkeysResScaleUpHotkey": "Увеличить разрешение:", "SettingsTabHotkeysResScaleDownHotkey": "Уменьшить разрешение:", "UserProfilesName": "Имя:", "UserProfilesUserId": "ID пользователя:", "SettingsTabGraphicsBackend": "Графический бэкенд", - "SettingsTabGraphicsBackendTooltip": "Выберите бэкенд, который будет использован в эмуляторе.\n\nVulkan является лучшим выбором для всех современных графических карт с актуальными драйверами. В Vulkan также включена более быстрая компиляция шейдеров (меньше статтеров) для всех GPU.\n\nOpenGL может достичь лучших результатов на старых GPU Nvidia, на старых GPU AMD на Linux или на GPU с небольшим количеством VRAM, хотя статтеров при компиляции шейдеров будут больше.\n\nРекомендуется использовать Vulkan. Используйте OpenGL, если ваш GPU не поддерживает Vulkan даже с актуальными драйверами.", + "SettingsTabGraphicsBackendTooltip": "Выберите бэкенд, который будет использован в эмуляторе.\n\nVulkan является лучшим выбором для всех современных графических карт с актуальными драйверами. В Vulkan также включена более быстрая компиляция шейдеров (меньше статтеров) для всех GPU.\n\nOpenGL может достичь лучших результатов на старых GPU Nvidia, на старых GPU AMD на Linux или на GPU с небольшим количеством VRAM, хотя статтеров при компиляции шейдеров будет больше.\n\nРекомендуется использовать Vulkan. Используйте OpenGL, если ваш GPU не поддерживает Vulkan даже с актуальными драйверами.", "SettingsEnableTextureRecompression": "Включить пережатие текстур", - "SettingsEnableTextureRecompressionTooltip": "Сжатие ASTC текстур для уменьшения использования VRAM. \n\nИгры, использующие этот формат текстур, включая Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder и The Legend of Zelda: Tears of the Kingdom. \n\nНа GPU с 4GiB VRAM или менее возможны вылеты при запуске этих игр. \n\nВключите, только если у вас заканчивается VRAM в вышеупомянутых играх. Рекомендуется оставить выключенным.", + "SettingsEnableTextureRecompressionTooltip": "Сжатие ASTC текстур для уменьшения использования VRAM. \n\nИгры, использующие этот формат текстур: Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder и The Legend of Zelda: Tears of the Kingdom. \n\nНа GPU с 4GiB VRAM или менее возможны вылеты при запуске этих игр. \n\nВключите, только если у вас заканчивается VRAM в вышеупомянутых играх. Рекомендуется оставить выключенным.", "SettingsTabGraphicsPreferredGpu": "Предпочтительный GPU", - "SettingsTabGraphicsPreferredGpuTooltip": "Выберите графический процессор, которая будет использоваться с графическим бэкендом Vulkan.\n\nЭта настройка не влияет на графический процессор, который будет использовать OpenGL.\n\nЕсли вы не уверены что нужно выбрать, используйте графический процессор, помеченный как \"dGPU\". Если его нет, оставьте выбор по умолчанию.", + "SettingsTabGraphicsPreferredGpuTooltip": "Выберите GPU, который будет использоваться с графическим бэкендом Vulkan.\n\nЭта настройка не влияет на GPU, который будет использовать OpenGL.\n\nЕсли вы не уверены что нужно выбрать, используйте графический процессор, помеченный как \"dGPU\". Если его нет, оставьте выбор по умолчанию.", "SettingsAppRequiredRestartMessage": "Требуется перезапуск Ryujinx", "SettingsGpuBackendRestartMessage": "Графический бэкенд или настройки графического процессора были изменены. Требуется перезапуск для вступления в силу изменений.", "SettingsGpuBackendRestartSubMessage": "Перезапустить сейчас?", @@ -633,7 +633,7 @@ "SettingsEnableColorSpacePassthroughTooltip": "Направляет бэкенд Vulkan на передачу информации о цвете без указания цветового пространства. Для пользователей с экранами с расширенной гаммой данная настройка приводит к получению более ярких цветов за счет снижения корректности цветопередачи.", "VolumeShort": "Громкость", "UserProfilesManageSaves": "Управление сохранениями", - "DeleteUserSave": "Вы хотите удалить сохранение пользователя для этой игры?", + "DeleteUserSave": "Вы хотите удалить сохранения для этой игры?", "IrreversibleActionNote": "Данное действие является необратимым.", "SaveManagerHeading": "Редактирование сохранений для {0} ({1})", "SaveManagerTitle": "Менеджер сохранений", @@ -647,7 +647,10 @@ "GraphicsAATooltip": "Применимое сглаживание для рендера.\n\nFXAA размывает большую часть изображения, SMAA попытается найти \"зазубренные\" края и сгладить их.\n\nНе рекомендуется использовать вместе с масштабирующим фильтром FSR.\n\nЭта опция может быть изменена во время игры по нажатию \"Применить\" ниже; Вы можете просто переместить окно настроек в сторону и поэкспериментировать, пока не найдёте подходящую настройку игры.\n\nРекомендуется использовать \"Нет\".", "GraphicsAALabel": "Сглаживание:", "GraphicsScalingFilterLabel": "Интерполяция:", - "GraphicsScalingFilterTooltip": "Выберите фильтрацию, которая будет применяться при масштабировании.\n\n\"Билинейная\" хорошо работает для 3D-игр и является настройкой по умолчанию.\n\n\"Точный\" рекомендуется для пиксельных .\n\n\"FSR\" это фильтр резкости, который не рекомендуется использовать с FXAA или SMAA.\n\nЭта опция может быть изменена во время игры по нажатию кнопки \"Применить\" ниже; Вы можете просто переместить окно настроек в сторону и поэкспериментировать, пока не подберете подходящие настройки для конкретной игры.\n\nРекомендуется использовать \"Билинейная\".", + "GraphicsScalingFilterTooltip": "Выберите фильтрацию, которая будет применяться при масштабировании.\n\n\"Билинейная\" хорошо работает для 3D-игр и является настройкой по умолчанию.\n\n\"Ступенчатая\" рекомендуется для пиксельных игр.\n\n\"FSR\" это фильтр резкости, который не рекомендуется использовать с FXAA или SMAA.\n\nЭта опция может быть изменена во время игры по нажатию кнопки \"Применить\" ниже; Вы можете просто переместить окно настроек в сторону и поэкспериментировать, пока не подберете подходящие настройки для конкретной игры.\n\nРекомендуется использовать \"Билинейная\".", + "GraphicsScalingFilterBilinear": "Билинейная", + "GraphicsScalingFilterNearest": "Ступенчатая", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "Уровень", "GraphicsScalingFilterLevelTooltip": "Выбор режима работы FSR 1.0. Выше - четче.", "SmaaLow": "SMAA Низкое", @@ -656,13 +659,15 @@ "SmaaUltra": "SMAA Ультра", "UserEditorTitle": "Редактирование пользователя", "UserEditorTitleCreate": "Создание пользователя", - "SettingsTabNetworkInterface": "Сетевой Интерфейс", - "NetworkInterfaceTooltip": "Сетевой интерфейс, используемый для функций LAN/LDN.\n\nВ сочетании с VPN или XLink Kai и игрой с поддержкой LAN, может использоваться для создания локальной сети через интернет.\n\nРекомендуется использовать \"По умолчанию\".", + "SettingsTabNetworkInterface": "Сетевой интерфейс:", + "NetworkInterfaceTooltip": "Сетевой интерфейс, используемый для функций LAN/LDN.\n\nМожет использоваться для игры через интернет в сочетании с VPN или XLink Kai и игрой с поддержкой LAN.\n\nРекомендуется использовать \"По умолчанию\".", "NetworkInterfaceDefault": "По умолчанию", "PackagingShaders": "Упаковка шейдеров", "AboutChangelogButton": "Список изменений на GitHub", "AboutChangelogButtonTooltipMessage": "Нажмите, чтобы открыть список изменений для этой версии", "SettingsTabNetworkMultiplayer": "Мультиплеер", "MultiplayerMode": "Режим:", - "MultiplayerModeTooltip": "Изменение многопользовательского режима LDN.\n\nLdnMitm модифицирует функциональность локальной беспроводной/игры на одном устройстве в играх, позволяя играть с другими пользователями Ryujinx или взломанными консолями Nintendo Switch с установленным модулем ldn_mitm, находящимися в одной локальной сети друг с другом.\n\nМногопользовательская игра требует наличия у всех игроков одной и той же версии игры (т.е. Super Smash Bros. Ultimate v13.0.1 не может подключиться к v13.0.0).\n\nРекомендуется оставить отключенным." + "MultiplayerModeTooltip": "Изменение многопользовательского режима LDN.\n\nLdnMitm модифицирует функциональность локальной беспроводной/игры на одном устройстве в играх, позволяя играть с другими пользователями Ryujinx или взломанными консолями Nintendo Switch с установленным модулем ldn_mitm, находящимися в одной локальной сети друг с другом.\n\nМногопользовательская игра требует наличия у всех игроков одной и той же версии игры (т.е. Super Smash Bros. Ultimate v13.0.1 не может подключиться к v13.0.0).\n\nРекомендуется оставить отключенным.", + "MultiplayerModeDisabled": "Отключено", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/th_TH.json b/src/Ryujinx/Assets/Locales/th_TH.json index e2132f1a45..23745ad94c 100644 --- a/src/Ryujinx/Assets/Locales/th_TH.json +++ b/src/Ryujinx/Assets/Locales/th_TH.json @@ -648,6 +648,9 @@ "GraphicsAALabel": "ลดการฉีกขาดของภาพ:", "GraphicsScalingFilterLabel": "ปรับขนาดตัวกรอง:", "GraphicsScalingFilterTooltip": "เลือกตัวกรองสเกลที่จะใช้เมื่อใช้สเกลความละเอียด\n\nBilinear ทำงานได้ดีกับเกม 3D และเป็นตัวเลือกเริ่มต้นที่ปลอดภัย\n\nแนะนำให้ใช้เกมภาพพิกเซลที่ใกล้เคียงที่สุด\n\nFSR 1.0 เป็นเพียงตัวกรองความคมชัด ไม่แนะนำให้ใช้กับ FXAA หรือ SMAA\n\nตัวเลือกนี้สามารถเปลี่ยนแปลงได้ในขณะที่เกมกำลังทำงานอยู่โดยคลิก \"นำไปใช้\" ด้านล่าง คุณสามารถย้ายหน้าต่างการตั้งค่าไปด้านข้างและทดลองจนกว่าคุณจะพบรูปลักษณ์ที่คุณต้องการสำหรับเกม", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "ระดับ", "GraphicsScalingFilterLevelTooltip": "ตั้งค่าระดับความคมชัด FSR 1.0 สูงกว่าจะคมชัดกว่า", "SmaaLow": "SMAA ต่ำ", @@ -664,5 +667,7 @@ "AboutChangelogButtonTooltipMessage": "คลิกเพื่อเปิดบันทึกการเปลี่ยนแปลงสำหรับเวอร์ชั่นนี้ บนเบราว์เซอร์เริ่มต้นของคุณ", "SettingsTabNetworkMultiplayer": "ผู้เล่นหลายคน", "MultiplayerMode": "โหมด:", - "MultiplayerModeTooltip": "เปลี่ยนโหมดผู้เล่นหลายคนของ LDN\n\nLdnMitm จะปรับเปลี่ยนฟังก์ชันการเล่นแบบไร้สาย/ภายใน จะให้เกมทำงานเหมือนกับว่าเป็น LAN ช่วยให้สามารถเชื่อมต่อภายในเครือข่ายเดียวกันกับอินสแตนซ์ Ryujinx อื่น ๆ และคอนโซล Nintendo Switch ที่ถูกแฮ็กซึ่งมีโมดูล ldn_mitm ติดตั้งอยู่\n\nผู้เล่นหลายคนต้องการให้ผู้เล่นทุกคนอยู่ในเกมเวอร์ชันเดียวกัน (เช่น Super Smash Bros. Ultimate v13.0.1 ไม่สามารถเชื่อมต่อกับ v13.0.0)\n\nปล่อยให้ปิดการใช้งานหากไม่แน่ใจ" + "MultiplayerModeTooltip": "เปลี่ยนโหมดผู้เล่นหลายคนของ LDN\n\nLdnMitm จะปรับเปลี่ยนฟังก์ชันการเล่นแบบไร้สาย/ภายใน จะให้เกมทำงานเหมือนกับว่าเป็น LAN ช่วยให้สามารถเชื่อมต่อภายในเครือข่ายเดียวกันกับอินสแตนซ์ Ryujinx อื่น ๆ และคอนโซล Nintendo Switch ที่ถูกแฮ็กซึ่งมีโมดูล ldn_mitm ติดตั้งอยู่\n\nผู้เล่นหลายคนต้องการให้ผู้เล่นทุกคนอยู่ในเกมเวอร์ชันเดียวกัน (เช่น Super Smash Bros. Ultimate v13.0.1 ไม่สามารถเชื่อมต่อกับ v13.0.0)\n\nปล่อยให้ปิดการใช้งานหากไม่แน่ใจ", + "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/tr_TR.json b/src/Ryujinx/Assets/Locales/tr_TR.json index 569ed28b1e..bfb5cb53a2 100644 --- a/src/Ryujinx/Assets/Locales/tr_TR.json +++ b/src/Ryujinx/Assets/Locales/tr_TR.json @@ -341,7 +341,7 @@ "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 yazılımı yüklemek ister misiniz? (Firmware {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "Yüklü firmware bulunamadı ancak Ryujinx sağlanan oyundan {0} firmware sürümünü yükledi.\nEmülatör şimdi başlatılacak.", + "DialogFirmwareInstallEmbeddedSuccessMessage": "No installed firmware was found but Ryujinx was able to install firmware {0} from the provided game.\nThe emulator will now start.", "DialogFirmwareNoFirmwareInstalledMessage": "Yazılım Yüklü Değil", "DialogFirmwareInstalledMessage": "Yazılım {0} yüklendi", "DialogInstallFileTypesSuccessMessage": "Dosya uzantıları başarıyla yüklendi!", @@ -648,6 +648,9 @@ "GraphicsAALabel": "Kenar Yumuşatma:", "GraphicsScalingFilterLabel": "Ölçekleme Filtresi:", "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "Seviye", "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "Düşük SMAA", @@ -664,5 +667,7 @@ "AboutChangelogButtonTooltipMessage": "Kullandığınız versiyon için olan değişiklikleri varsayılan tarayıcınızda görmek için tıklayın", "SettingsTabNetworkMultiplayer": "Çok Oyunculu", "MultiplayerMode": "Mod:", - "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.", + "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/uk_UA.json b/src/Ryujinx/Assets/Locales/uk_UA.json index a77135dca4..dcf85eae9e 100644 --- a/src/Ryujinx/Assets/Locales/uk_UA.json +++ b/src/Ryujinx/Assets/Locales/uk_UA.json @@ -14,7 +14,7 @@ "MenuBarFileOpenEmuFolder": "Відкрити теку Ryujinx", "MenuBarFileOpenLogsFolder": "Відкрити теку журналів змін", "MenuBarFileExit": "_Вихід", - "MenuBarOptions": "_Options", + "MenuBarOptions": "_Параметри", "MenuBarOptionsToggleFullscreen": "На весь екран", "MenuBarOptionsStartGamesInFullscreen": "Запускати ігри на весь екран", "MenuBarOptionsStopEmulation": "Зупинити емуляцію", @@ -30,7 +30,7 @@ "MenuBarToolsManageFileTypes": "Керувати типами файлів", "MenuBarToolsInstallFileTypes": "Установити типи файлів", "MenuBarToolsUninstallFileTypes": "Видалити типи файлів", - "MenuBarHelp": "_Help", + "MenuBarHelp": "_Допомога", "MenuBarHelpCheckForUpdates": "Перевірити оновлення", "MenuBarHelpAbout": "Про застосунок", "MenuSearch": "Пошук...", @@ -44,7 +44,7 @@ "GameListHeaderFileExtension": "Розширення файлу", "GameListHeaderFileSize": "Розмір файлу", "GameListHeaderPath": "Шлях", - "GameListContextMenuOpenUserSaveDirectory": "Відкрити каталог збереження користувача", + "GameListContextMenuOpenUserSaveDirectory": "Відкрити теку збереження користувача", "GameListContextMenuOpenUserSaveDirectoryToolTip": "Відкриває каталог, який містить збереження користувача програми", "GameListContextMenuOpenDeviceSaveDirectory": "Відкрити каталог пристроїв користувача", "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Відкриває каталог, який містить збереження пристрою програми", @@ -72,11 +72,11 @@ "GameListContextMenuExtractDataLogoToolTip": "Видобуває розділ логотипу з поточної конфігурації програми (включаючи оновлення)", "GameListContextMenuCreateShortcut": "Створити ярлик застосунку", "GameListContextMenuCreateShortcutToolTip": "Створити ярлик на робочому столі, який запускає вибраний застосунок", - "GameListContextMenuCreateShortcutToolTipMacOS": "Create a shortcut in macOS's Applications folder that launches the selected Application", + "GameListContextMenuCreateShortcutToolTipMacOS": "Створити ярлик у каталозі macOS програм, що запускає обраний Додаток", "GameListContextMenuOpenModsDirectory": "Відкрити теку з модами", - "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", - "GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", + "GameListContextMenuOpenModsDirectoryToolTip": "Відкриває каталог, який містить модифікації Додатків", + "GameListContextMenuOpenSdModsDirectory": "Відкрити каталог модифікацій Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Відкриває альтернативний каталог SD-карти Atmosphere, що містить модифікації Додатків. Корисно для модифікацій, зроблених для реального обладнання.", "StatusBarGamesLoaded": "{0}/{1} ігор завантажено", "StatusBarSystemVersion": "Версія системи: {0}", "LinuxVmMaxMapCountDialogTitle": "Виявлено низьку межу для відображення памʼяті", @@ -155,7 +155,7 @@ "SettingsTabGraphicsResolutionScaleNative": "Стандартний (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Not recommended)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Не рекомендується)", "SettingsTabGraphicsAspectRatio": "Співвідношення сторін:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -384,10 +384,10 @@ "DialogUserProfileUnsavedChangesSubMessage": "Бажаєте скасувати зміни?", "DialogControllerSettingsModifiedConfirmMessage": "Поточні налаштування контролера оновлено.", "DialogControllerSettingsModifiedConfirmSubMessage": "Ви хочете зберегти?", - "DialogLoadFileErrorMessage": "{0}. Errored File: {1}", - "DialogModAlreadyExistsMessage": "Mod already exists", - "DialogModInvalidMessage": "The specified directory does not contain a mod!", - "DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!", + "DialogLoadFileErrorMessage": "{0}. Файл з помилкою: {1}", + "DialogModAlreadyExistsMessage": "Модифікація вже існує", + "DialogModInvalidMessage": "Вказаний каталог не містить модифікації!", + "DialogModDeleteNoParentMessage": "Не видалено: Не знайдено батьківський каталог для модифікації \"{0}\"!", "DialogDlcNoDlcErrorMessage": "Зазначений файл не містить DLC для вибраного заголовку!", "DialogPerformanceCheckLoggingEnabledMessage": "Ви увімкнули журнал налагодження, призначений лише для розробників.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Для оптимальної продуктивності рекомендується вимкнути ведення журналу налагодження. Ви хочете вимкнути ведення журналу налагодження зараз?", @@ -398,8 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "Зазначений файл не містить оновлення для вибраного заголовка!", "DialogSettingsBackendThreadingWarningTitle": "Попередження - потокове керування сервером", "DialogSettingsBackendThreadingWarningMessage": "Ryujinx потрібно перезапустити після зміни цього параметра, щоб він застосовувався повністю. Залежно від вашої платформи вам може знадобитися вручну вимкнути власну багатопотоковість драйвера під час використання Ryujinx.", - "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", - "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionWarningMessage": "Ви збираєтесь видалити модифікацію: {0}\n\nВи дійсно бажаєте продовжити?", + "DialogModManagerDeletionAllWarningMessage": "Ви збираєтесь видалити всі модифікації для цього Додатка.\n\nВи дійсно бажаєте продовжити?", "SettingsTabGraphicsFeaturesOptions": "Особливості", "SettingsTabGraphicsBackendMultithreading": "Багатопотоковість графічного сервера:", "CommonAuto": "Авто", @@ -452,13 +452,13 @@ "CustomThemePathTooltip": "Шлях до користувацької теми графічного інтерфейсу", "CustomThemeBrowseTooltip": "Огляд користувацької теми графічного інтерфейсу", "DockModeToggleTooltip": "У режимі док-станції емульована система веде себе як приєднаний Nintendo Switch. Це покращує точність графіки в більшості ігор. І навпаки, вимкнення цього призведе до того, що емульована система поводитиметься як портативний комутатор Nintendo, погіршуючи якість графіки.\n\nНалаштуйте елементи керування для гравця 1, якщо плануєте використовувати режим док-станції; налаштуйте ручні елементи керування, якщо плануєте використовувати портативний режим.\n\nЗалиште увімкненим, якщо не впевнені.", - "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", - "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", + "DirectKeyboardTooltip": "Підтримка прямого доступу до клавіатури (HID). Надає іграм доступ до клавіатури для вводу тексту.\n\nПрацює тільки з іграми, які підтримують клавіатуру на обладнанні Switch.\n\nЗалиште вимкненим, якщо не впевнені.", + "DirectMouseTooltip": "Підтримка прямого доступу до миші (HID). Надає іграм доступ до миші, як пристрій вказування.\n\nПрацює тільки з іграми, які підтримують мишу на обладнанні Switch, їх небагато.\n\nФункціонал сенсорного екрана може не працювати, якщо функція ввімкнена.\n\nЗалиште вимкненим, якщо не впевнені.", "RegionTooltip": "Змінити регіон системи", "LanguageTooltip": "Змінити мову системи", "TimezoneTooltip": "Змінити часовий пояс системи", "TimeTooltip": "Змінити час системи", - "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", + "VSyncToggleTooltip": "Емульована вертикальна синхронізація консолі. По суті, обмежувач кадрів для більшості ігор; його вимкнення може призвести до того, що ігри працюватимуть на вищій швидкості, екрани завантаження триватимуть довше чи зупинятимуться.\n\nМожна перемикати в грі гарячою клавішею (За умовчанням F1). Якщо ви плануєте вимкнути функцію, рекомендуємо зробити це через гарячу клавішу.\n\nЗалиште увімкненим, якщо не впевнені.", "PptcToggleTooltip": "Зберігає перекладені функції JIT, щоб їх не потрібно було перекладати кожного разу, коли гра завантажується.\n\nЗменшує заїкання та значно прискорює час завантаження після першого завантаження гри.\n\nЗалиште увімкненим, якщо не впевнені.", "FsIntegrityToggleTooltip": "Перевіряє наявність пошкоджених файлів під час завантаження гри, і якщо виявлено пошкоджені файли, показує помилку хешу в журналі.\n\nНе впливає на продуктивність і призначений для усунення несправностей.\n\nЗалиште увімкненим, якщо не впевнені.", "AudioBackendTooltip": "Змінює серверну частину, яка використовується для відтворення аудіо.\n\nSDL2 є кращим, тоді як OpenAL і SoundIO використовуються як резервні варіанти. Dummy не матиме звуку.\n\nВстановіть SDL2, якщо не впевнені.", @@ -472,10 +472,10 @@ "GraphicsBackendThreadingTooltip": "Виконує команди графічного сервера в другому потоці.\n\nПрискорює компіляцію шейдерів, зменшує затримки та покращує продуктивність драйверів GPU без власної підтримки багатопоточності. Трохи краща продуктивність на драйверах з багатопотоковістю.\nВстановіть значення «Авто», якщо не впевнені", "GalThreadingTooltip": "Виконує команди графічного сервера в другому потоці.\n\nПрискорює компіляцію шейдерів, зменшує затримки та покращує продуктивність драйверів GPU без власної підтримки багатопоточності. Трохи краща продуктивність на драйверах з багатопотоковістю.\n\nВстановіть значення «Авто», якщо не впевнені.", "ShaderCacheToggleTooltip": "Зберігає кеш дискового шейдера, що зменшує затримки під час наступних запусків.\n\nЗалиште увімкненим, якщо не впевнені.", - "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", + "ResolutionScaleTooltip": "Множить роздільну здатність гри.\n\nДеякі ігри можуть не працювати з цією функцією, і виглядатимуть піксельними; для цих ігор треба знайти модифікації, що зупиняють згладжування або підвищують роздільну здатність. Для останніх модифікацій, вибирайте \"Native\".\n\nЦей параметр можна міняти коли гра запущена кліком на \"Застосувати\"; ви можете перемістити вікно налаштувань і поекспериментувати з видом гри.\n\nМайте на увазі, що 4x це занадто для будь-якого комп'ютера.", "ResolutionScaleEntryTooltip": "Масштаб роздільної здатності з плаваючою комою, наприклад 1,5. Не інтегральні масштаби, швидше за все, спричинять проблеми або збій.", - "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", - "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", + "AnisotropyTooltip": "Рівень анізотропної фільтрації. Встановіть на «Авто», щоб використовувати значення, яке вимагає гра.", + "AspectRatioTooltip": "Співвідношення сторін застосовано до вікна рендера.\n\nМіняйте тільки, якщо використовуєте модифікацію співвідношення сторін для гри, інакше графіка буде розтягнута.\n\nЗалиште на \"16:9\", якщо не впевнені.", "ShaderDumpPathTooltip": "Шлях скидання графічних шейдерів", "FileLogTooltip": "Зберігає журнал консолі у файл журналу на диску. Не впливає на продуктивність.", "StubLogTooltip": "Друкує повідомлення журналу-заглушки на консолі. Не впливає на продуктивність.", @@ -553,9 +553,9 @@ "SoftwareKeyboardModeAlphabet": "Повинно бути лише не CJK-символи", "SoftwareKeyboardModeASCII": "Повинно бути лише ASCII текст", "ControllerAppletControllers": "Підтримувані контролери:", - "ControllerAppletPlayers": "Players:", - "ControllerAppletDescription": "Your current configuration is invalid. Open settings and reconfigure your inputs.", - "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.", + "ControllerAppletPlayers": "Гравці:", + "ControllerAppletDescription": "Поточна конфігурація невірна. Відкрийте налаштування та переналаштуйте Ваші дані.", + "ControllerAppletDocked": "Встановлений режим в док-станції. Вимкніть портативні контролери.", "UpdaterRenaming": "Перейменування старих файлів...", "UpdaterRenameFailed": "Програмі оновлення не вдалося перейменувати файл: {0}", "UpdaterAddingFiles": "Додавання нових файлів...", @@ -616,11 +616,11 @@ "UserProfilesName": "Імʼя", "UserProfilesUserId": "ID користувача:", "SettingsTabGraphicsBackend": "Графічний сервер", - "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", + "SettingsTabGraphicsBackendTooltip": "Виберіть backend графіки, що буде використовуватись в емуляторі.\n\n\"Vulkan\" краще для всіх сучасних відеокарт, якщо драйвери вчасно оновлюються. У Vulkan також швидше компілюються шейдери (менше \"заїкання\" зображення) на відеокартах всіх компаній.\n\n\"OpenGL\" може дати кращі результати на старих відеокартах Nvidia, старих відеокартах AMD на Linux, або на відеокартах з маленькою кількістю VRAM, але \"заїкання\" через компіляцію шейдерів будуть частіші.\n\nЯкщо не впевнені, встановіть на \"Vulkan\". Встановіть на \"OpenGL\", якщо Ваша відеокарта не підтримує Vulkan навіть на останніх драйверах.", "SettingsEnableTextureRecompression": "Увімкнути рекомпресію текстури", - "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", + "SettingsEnableTextureRecompressionTooltip": "Стискає текстури ASTC, щоб зменшити використання VRAM.\n\nЦим форматом текстур користуються такі ігри, як Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder і The Legend of Zelda: Tears of the Kingdom.\n\nЦі ігри, скоріше всього крашнуться на відеокартах з розміром VRAM в 4 Гб і менше.\n\nВмикайте тільки якщо у Вас закінчується VRAM на цих іграх. Залиште на \"Вимкнути\", якщо не впевнені.", "SettingsTabGraphicsPreferredGpu": "Бажаний GPU", - "SettingsTabGraphicsPreferredGpuTooltip": "Виберіть відеокарту, яка використовуватиметься з графічним сервером Vulkan.\n\nНе впливає на графічний процесор, який використовуватиме OpenGL.\n\nЯкщо не впевнені, встановіть графічний процесор, позначений як «dGPU». Якщо такого немає, залиште це.", + "SettingsTabGraphicsPreferredGpuTooltip": "Виберіть відеокарту, яка використовуватиметься з графічним сервером Vulkan.\n\nНе впливає на графічний процесор, який використовуватиме OpenGL.\n\nВстановіть графічний процесор, позначений як «dGPU», якщо не впевнені. Якщо такого немає, не чіпайте.", "SettingsAppRequiredRestartMessage": "Необхідно перезапустити Ryujinx", "SettingsGpuBackendRestartMessage": "Налаштування графічного сервера або GPU було змінено. Для цього знадобиться перезапуск", "SettingsGpuBackendRestartSubMessage": "Бажаєте перезапустити зараз?", @@ -644,12 +644,15 @@ "Recover": "Відновити", "UserProfilesRecoverHeading": "Знайдено збереження для наступних облікових записів", "UserProfilesRecoverEmptyList": "Немає профілів для відновлення", - "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", + "GraphicsAATooltip": "Застосовує згладження до рендера гри.\n\nFXAA розмиє більшість зображення, а SMAA спробує знайти нерівні краї та згладити їх.\n\nНе рекомендується використовувати разом з фільтром масштабування FSR.\n\nЦю опцію можна міняти коли гра запущена кліком на \"Застосувати; ви можете відсунути вікно налаштувань і поекспериментувати з видом гри.\n\nЗалиште на \"Немає\", якщо не впевнені.", "GraphicsAALabel": "Згладжування:", "GraphicsScalingFilterLabel": "Фільтр масштабування:", - "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterTooltip": "Виберіть фільтр масштабування, що використається при збільшенні роздільної здатності.\n\n\"Білінійний\" добре виглядає в 3D іграх, і хороше налаштування за умовчуванням.\n\n\"Найближчий\" рекомендується для ігор з піксель-артом.\n\n\"FSR 1.0\" - це просто фільтр різкості, не рекомендується використовувати разом з FXAA або SMAA.\n\nЦю опцію можна міняти коли гра запущена кліком на \"Застосувати; ви можете відсунути вікно налаштувань і поекспериментувати з видом гри.\n\nЗалиште на \"Білінійний\", якщо не впевнені.", + "GraphicsScalingFilterBilinear": "Білінійний", + "GraphicsScalingFilterNearest": "Найближчий", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "Рівень", - "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", + "GraphicsScalingFilterLevelTooltip": "Встановити рівень різкості в FSR 1.0. Чим вище - тим різкіше.", "SmaaLow": "SMAA Низький", "SmaaMedium": "SMAA Середній", "SmaaHigh": "SMAA Високий", @@ -657,12 +660,14 @@ "UserEditorTitle": "Редагувати користувача", "UserEditorTitleCreate": "Створити користувача", "SettingsTabNetworkInterface": "Мережевий інтерфейс:", - "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", + "NetworkInterfaceTooltip": "Мережевий інтерфейс, що використовується для LAN/LDN.\n\nРазом з VPN або XLink Kai, і грою що підтримує LAN, може імітувати з'єднання в однаковій мережі через Інтернет.", "NetworkInterfaceDefault": "Стандартний", "PackagingShaders": "Пакування шейдерів", "AboutChangelogButton": "Переглянути журнал змін на GitHub", "AboutChangelogButtonTooltipMessage": "Клацніть, щоб відкрити журнал змін для цієї версії у стандартному браузері.", "SettingsTabNetworkMultiplayer": "Мережева гра", "MultiplayerMode": "Режим:", - "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." + "MultiplayerModeTooltip": "Змінити LDN мультиплеєру.\n\nLdnMitm змінить функціонал бездротової/локальної гри в іграх, щоб вони працювали так, ніби це LAN, що дозволяє локальні підключення в тій самій мережі з іншими екземплярами Ryujinx та хакнутими консолями Nintendo Switch, які мають встановлений модуль ldn_mitm.\n\nМультиплеєр вимагає, щоб усі гравці були на одній і тій же версії гри (наприклад Super Smash Bros. Ultimate v13.0.1 не зможе під'єднатися до v13.0.0).\n\nЗалиште на \"Вимкнено\", якщо не впевнені, ", + "MultiplayerModeDisabled": "Вимкнено", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/zh_CN.json b/src/Ryujinx/Assets/Locales/zh_CN.json index fcd76f50d8..cc1d583e10 100644 --- a/src/Ryujinx/Assets/Locales/zh_CN.json +++ b/src/Ryujinx/Assets/Locales/zh_CN.json @@ -11,8 +11,8 @@ "MenuBarFile": "文件", "MenuBarFileOpenFromFile": "加载游戏文件", "MenuBarFileOpenUnpacked": "加载解包后的游戏", - "MenuBarFileOpenEmuFolder": "打开 Ryujinx 系统文件夹", - "MenuBarFileOpenLogsFolder": "打开日志文件夹", + "MenuBarFileOpenEmuFolder": "打开 Ryujinx 系统目录", + "MenuBarFileOpenLogsFolder": "打开日志目录", "MenuBarFileExit": "退出", "MenuBarOptions": "选项", "MenuBarOptionsToggleFullscreen": "切换全屏", @@ -24,9 +24,9 @@ "MenuBarOptionsSimulateWakeUpMessage": "模拟唤醒消息", "MenuBarActionsScanAmiibo": "扫描 Amiibo", "MenuBarTools": "工具", - "MenuBarToolsInstallFirmware": "安装固件", - "MenuBarFileToolsInstallFirmwareFromFile": "从 XCI 或 ZIP 安装固件", - "MenuBarFileToolsInstallFirmwareFromDirectory": "从文件夹安装固件", + "MenuBarToolsInstallFirmware": "安装系统固件", + "MenuBarFileToolsInstallFirmwareFromFile": "从 XCI 或 ZIP 文件中安装系统固件", + "MenuBarFileToolsInstallFirmwareFromDirectory": "从文件夹中安装系统固件", "MenuBarToolsManageFileTypes": "管理文件扩展名", "MenuBarToolsInstallFileTypes": "关联文件扩展名", "MenuBarToolsUninstallFileTypes": "取消关联扩展名", @@ -45,24 +45,24 @@ "GameListHeaderFileSize": "大小", "GameListHeaderPath": "路径", "GameListContextMenuOpenUserSaveDirectory": "打开用户存档目录", - "GameListContextMenuOpenUserSaveDirectoryToolTip": "打开储存游戏存档的目录", - "GameListContextMenuOpenDeviceSaveDirectory": "打开系统目录", - "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "打开包含游戏系统设置的目录", - "GameListContextMenuOpenBcatSaveDirectory": "打开 BCAT 目录", - "GameListContextMenuOpenBcatSaveDirectoryToolTip": "打开包含游戏 BCAT 数据的目录", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "打开储存游戏用户存档的目录", + "GameListContextMenuOpenDeviceSaveDirectory": "打开系统数据目录", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "打开储存游戏系统数据的目录", + "GameListContextMenuOpenBcatSaveDirectory": "打开 BCAT 数据目录", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "打开储存游戏 BCAT 数据的目录", "GameListContextMenuManageTitleUpdates": "管理游戏更新", "GameListContextMenuManageTitleUpdatesToolTip": "打开游戏更新管理窗口", "GameListContextMenuManageDlc": "管理 DLC", "GameListContextMenuManageDlcToolTip": "打开 DLC 管理窗口", "GameListContextMenuCacheManagement": "缓存管理", - "GameListContextMenuCacheManagementPurgePptc": "清除已编译的 PPTC 文件", - "GameListContextMenuCacheManagementPurgePptcToolTip": "仅删除 PPTC 转换后的文件,下次打开游戏时将根据 .info 文件重新生成 PPTC 文件。\n如想彻底清除缓存,请进入CPU目录把 .info 文件一并删除", - "GameListContextMenuCacheManagementPurgeShaderCache": "清除着色器缓存", - "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "删除游戏的着色器缓存", - "GameListContextMenuCacheManagementOpenPptcDirectory": "打开 PPTC 目录", - "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "打开包含游戏 PPTC 缓存的目录", + "GameListContextMenuCacheManagementPurgePptc": "清除 PPTC 缓存文件", + "GameListContextMenuCacheManagementPurgePptcToolTip": "删除游戏的 PPTC 缓存文件,下次启动游戏时重新编译生成 PPTC 缓存文件", + "GameListContextMenuCacheManagementPurgeShaderCache": "清除着色器缓存文件", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "删除游戏的着色器缓存文件,下次启动游戏时重新生成着色器缓存文件", + "GameListContextMenuCacheManagementOpenPptcDirectory": "打开 PPTC 缓存目录", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "打开储存游戏 PPTC 缓存文件的目录", "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "打开着色器缓存目录", - "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "打开包含游戏着色器缓存的目录", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "打开储存游戏着色器缓存文件的目录", "GameListContextMenuExtractData": "提取数据", "GameListContextMenuExtractDataExeFS": "ExeFS", "GameListContextMenuExtractDataExeFSToolTip": "从游戏的当前状态中提取 ExeFS 分区 (包括更新)", @@ -72,13 +72,13 @@ "GameListContextMenuExtractDataLogoToolTip": "从游戏的当前状态中提取图标 (包括更新)", "GameListContextMenuCreateShortcut": "创建游戏快捷方式", "GameListContextMenuCreateShortcutToolTip": "创建一个直接启动此游戏的桌面快捷方式", - "GameListContextMenuCreateShortcutToolTipMacOS": "在 macOS 的应用程序文件夹中创建一个直接启动此游戏的快捷方式", + "GameListContextMenuCreateShortcutToolTipMacOS": "在 macOS 的应用程序目录中创建一个直接启动此游戏的快捷方式", "GameListContextMenuOpenModsDirectory": "打开 MOD 目录", "GameListContextMenuOpenModsDirectoryToolTip": "打开存放游戏 MOD 的目录", "GameListContextMenuOpenSdModsDirectory": "打开大气层系统 MOD 目录", - "GameListContextMenuOpenSdModsDirectoryToolTip": "打开适用于大气层系统的游戏 MOD 目录,对于为真实硬件打包的 MOD 非常有用", + "GameListContextMenuOpenSdModsDirectoryToolTip": "打开存放适用于大气层系统的游戏 MOD 的目录,对于为真实硬件打包的 MOD 非常有用", "StatusBarGamesLoaded": "{0}/{1} 游戏加载完成", - "StatusBarSystemVersion": "系统版本:{0}", + "StatusBarSystemVersion": "系统固件版本:{0}", "LinuxVmMaxMapCountDialogTitle": "检测到操作系统内存映射最大数量被设置的过低", "LinuxVmMaxMapCountDialogTextPrimary": "你想要将操作系统 vm.max_map_count 的值增加到 {0} 吗", "LinuxVmMaxMapCountDialogTextSecondary": "有些游戏可能会尝试创建超过当前系统允许的内存映射最大数量,若超过当前最大数量,Ryujinx 模拟器将会闪退。", @@ -276,7 +276,7 @@ "ProfileNameSelectionWatermark": "输入昵称", "ProfileImageSelectionTitle": "选择头像", "ProfileImageSelectionHeader": "选择合适的头像图片", - "ProfileImageSelectionNote": "您可以导入自定义头像,或从模拟器系统中选择预设头像", + "ProfileImageSelectionNote": "您可以导入自定义头像,或从模拟器系统固件中选择预设头像", "ProfileImageSelectionImportImage": "导入图像文件", "ProfileImageSelectionSelectAvatar": "选择预设头像", "InputDialogTitle": "输入对话框", @@ -285,7 +285,7 @@ "InputDialogAddNewProfileTitle": "选择用户名称", "InputDialogAddNewProfileHeader": "请输入账户名称", "InputDialogAddNewProfileSubtext": "(最大长度:{0})", - "AvatarChoose": "选择头像", + "AvatarChoose": "保存选定头像", "AvatarSetBackgroundColor": "设置背景色", "AvatarClose": "关闭", "ControllerSettingsLoadProfileToolTip": "加载配置文件", @@ -297,7 +297,7 @@ "GameListContextMenuRunApplication": "启动游戏", "GameListContextMenuToggleFavorite": "收藏", "GameListContextMenuToggleFavoriteToolTip": "切换游戏的收藏状态", - "SettingsTabGeneralTheme": "主题︰", + "SettingsTabGeneralTheme": "主题:", "SettingsTabGeneralThemeDark": "深色(暗黑)", "SettingsTabGeneralThemeLight": "浅色(亮色)", "ControllerSettingsConfigureGeneral": "配置", @@ -305,7 +305,7 @@ "ControllerSettingsRumbleStrongMultiplier": "强震动幅度", "ControllerSettingsRumbleWeakMultiplier": "弱震动幅度", "DialogMessageSaveNotAvailableMessage": "没有{0} [{1:x16}]的游戏存档", - "DialogMessageSaveNotAvailableCreateSaveMessage": "是否创建该游戏的存档文件夹?", + "DialogMessageSaveNotAvailableCreateSaveMessage": "是否创建该游戏的存档?", "DialogConfirmationTitle": "Ryujinx - 确认", "DialogUpdaterTitle": "Ryujinx - 更新", "DialogErrorTitle": "Ryujinx - 错误", @@ -340,10 +340,10 @@ "DialogRestartRequiredMessage": "需要重启模拟器", "DialogThemeRestartMessage": "主题设置已保存,需要重启模拟器才能生效。", "DialogThemeRestartSubMessage": "是否要重启模拟器?", - "DialogFirmwareInstallEmbeddedMessage": "要安装游戏内置的系统固件吗?(固件 {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "未找到已安装的固件,Ryujinx 已经从当前游戏中安装了系统固件 {0} 。\n模拟器现在可以运行。", - "DialogFirmwareNoFirmwareInstalledMessage": "未安装固件", - "DialogFirmwareInstalledMessage": "已安装固件 {0}", + "DialogFirmwareInstallEmbeddedMessage": "要安装游戏文件中内嵌的系统固件吗?(固件版本 {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Ryujinx 模拟器已经从当前游戏文件中安装了系统固件 {0} 。\n模拟器现在可以正常运行了。", + "DialogFirmwareNoFirmwareInstalledMessage": "未安装系统固件", + "DialogFirmwareInstalledMessage": "已安装系统固件 {0}", "DialogInstallFileTypesSuccessMessage": "关联文件类型成功!", "DialogInstallFileTypesErrorMessage": "关联文件类型失败!", "DialogUninstallFileTypesSuccessMessage": "成功解除文件类型关联!", @@ -354,7 +354,7 @@ "DialogSoftwareKeyboardErrorExceptionMessage": "显示软件键盘时出错:{0}", "DialogErrorAppletErrorExceptionMessage": "显示错误对话框时出错:{0}", "DialogUserErrorDialogMessage": "{0}: {1}", - "DialogUserErrorDialogInfoMessage": "\n有关修复此错误的更多信息,可以遵循我们的安装指南。", + "DialogUserErrorDialogInfoMessage": "\n有关修复此错误的更多信息,可以查看我们的安装指南。", "DialogUserErrorDialogTitle": "Ryujinx 错误 ({0})", "DialogAmiiboApiTitle": "Amiibo API", "DialogAmiiboApiFailFetchMessage": "从 API 获取信息时出错。", @@ -364,19 +364,19 @@ "DialogProfileDeleteProfileTitle": "删除配置文件", "DialogProfileDeleteProfileMessage": "删除后不可恢复,确认删除吗?", "DialogWarning": "警告", - "DialogPPTCDeletionMessage": "您即将删除:\n\n{0} 的 PPTC 缓存\n\n确定吗?", - "DialogPPTCDeletionErrorMessage": "清除 {0} 的 PPTC 缓存时出错:{1}", - "DialogShaderDeletionMessage": "您即将删除:\n\n{0} 的着色器缓存\n\n确定吗?", - "DialogShaderDeletionErrorMessage": "清除 {0} 的着色器缓存时出错:{1}", + "DialogPPTCDeletionMessage": "您即将删除:\n\n{0} 的 PPTC 缓存文件\n\n确定吗?", + "DialogPPTCDeletionErrorMessage": "清除 {0} 的 PPTC 缓存文件时出错:{1}", + "DialogShaderDeletionMessage": "您即将删除:\n\n{0} 的着色器缓存文件\n\n确定吗?", + "DialogShaderDeletionErrorMessage": "清除 {0} 的着色器缓存文件时出错:{1}", "DialogRyujinxErrorMessage": "Ryujinx 模拟器发生错误", "DialogInvalidTitleIdErrorMessage": "用户界面错误:所选游戏没有有效的游戏 ID", - "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "在路径 {0} 中找不到有效的系统固件。", - "DialogFirmwareInstallerFirmwareInstallTitle": "安装固件 {0}", - "DialogFirmwareInstallerFirmwareInstallMessage": "即将安装系统版本 {0} 。", - "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n替换当前系统版本 {0} 。", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "在路径 {0} 中找不到有效的 Switch 系统固件。", + "DialogFirmwareInstallerFirmwareInstallTitle": "安装系统固件 {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "即将安装系统固件版本 {0} 。", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n替换当前系统固件版本 {0} 。", "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n是否继续?", - "DialogFirmwareInstallerFirmwareInstallWaitMessage": "安装固件中...", - "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "成功安装系统版本 {0} 。", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "安装系统固件中...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "成功安装系统固件版本 {0} 。", "DialogUserProfileDeletionWarningMessage": "删除后将没有可用的账户", "DialogUserProfileDeletionConfirmMessage": "是否删除所选账户", "DialogUserProfileUnsavedChangesTitle": "警告 - 有未保存的更改", @@ -386,7 +386,7 @@ "DialogControllerSettingsModifiedConfirmSubMessage": "是否保存?", "DialogLoadFileErrorMessage": "{0}. 错误的文件:{1}", "DialogModAlreadyExistsMessage": "MOD 已存在", - "DialogModInvalidMessage": "指定的目录找不到 MOD!", + "DialogModInvalidMessage": "指定的目录找不到 MOD 文件!", "DialogModDeleteNoParentMessage": "删除失败:找不到 MOD 的父目录“{0}”!", "DialogDlcNoDlcErrorMessage": "选择的文件不是当前游戏的 DLC!", "DialogPerformanceCheckLoggingEnabledMessage": "您启用了跟踪日志,该功能仅供开发人员使用。", @@ -429,7 +429,7 @@ "AmiiboOptionsUsRandomTagLabel": "修改:使用随机生成的Amiibo ID", "DlcManagerTableHeadingEnabledLabel": "已启用", "DlcManagerTableHeadingTitleIdLabel": "游戏 ID", - "DlcManagerTableHeadingContainerPathLabel": "文件夹路径", + "DlcManagerTableHeadingContainerPathLabel": "容器路径", "DlcManagerTableHeadingFullPathLabel": "完整路径", "DlcManagerRemoveAllButton": "全部删除", "DlcManagerEnableAllButton": "全部启用", @@ -440,8 +440,8 @@ "CommonSort": "排序", "CommonShowNames": "显示名称", "CommonFavorite": "收藏", - "OrderAscending": "从小到大", - "OrderDescending": "从大到小", + "OrderAscending": "升序", + "OrderDescending": "降序", "SettingsTabGraphicsFeatures": "功能与优化", "ErrorWindowTitle": "错误窗口", "ToggleDiscordTooltip": "选择是否在 Discord 中显示您的游玩状态", @@ -451,7 +451,7 @@ "CustomThemeCheckTooltip": "使用自定义的 Avalonia 主题作为模拟器菜单的外观", "CustomThemePathTooltip": "自定义主题的目录", "CustomThemeBrowseTooltip": "查找自定义主题", - "DockModeToggleTooltip": "启用 Switch 的主机模式,可以模拟 Switch 连接底座的情况,此时绝大多数游戏画质会提高,略微增加性能消耗。\n若禁用主机模式,则使用 Switch 的掌机模式,可以模拟手持 Switch 运行游戏的情况,游戏画质会降低,性能消耗也会降低。\n\n如果使用主机模式,请选择“玩家 1”的手柄设置;如果使用掌机模式,请选择“掌机模式”的手柄设置。\n\n如果不确定,请保持开启状态。", + "DockModeToggleTooltip": "启用 Switch 的主机模式(电视模式、底座模式),就是模拟 Switch 连接底座的情况;若禁用主机模式,则使用 Switch 的掌机模式,就是模拟手持 Switch 运行游戏的情况。\n对于绝大多数游戏而言,主机模式会比掌机模式,画质更高,同时性能消耗也更高。\n\n简而言之,想要更好画质则启用主机模式;电脑硬件性能不足则禁用主机模式。\n\n如果使用主机模式,请选择“玩家 1”的手柄设置;如果使用掌机模式,请选择“掌机模式”的手柄设置。\n\n如果不确定,请保持开启状态。", "DirectKeyboardTooltip": "直接键盘访问(HID)支持,游戏可以直接访问键盘作为文本输入设备。\n\n仅适用于在 Switch 硬件上原生支持键盘的游戏。\n\n如果不确定,请保持关闭状态。", "DirectMouseTooltip": "直接鼠标访问(HID)支持,游戏可以直接访问鼠标作为指针输入设备。\n\n只适用于在 Switch 硬件上原生支持鼠标控制的游戏,这种游戏很少。\n\n启用后,触屏功能可能无法正常工作。\n\n如果不确定,请保持关闭状态。", "RegionTooltip": "更改系统区域", @@ -459,19 +459,19 @@ "TimezoneTooltip": "更改系统时区", "TimeTooltip": "更改系统时间", "VSyncToggleTooltip": "模拟控制台的垂直同步,开启后会降低大部分游戏的帧率。关闭后,可以获得更高的帧率,但也可能导致游戏画面加载耗时更长或卡住。\n\n在游戏中可以使用热键进行切换(默认为 F1 键)。\n\n如果不确定,请保持开启状态。", - "PptcToggleTooltip": "缓存已编译的游戏指令,这样每次游戏加载时就无需重新编译。\n\n减少卡顿和启动时间,提高游戏响应速度。\n\n如果不确定,请保持开启状态。", + "PptcToggleTooltip": "缓存已编译的游戏指令,这样每次游戏加载时就无需重新编译。\n\n可以减少卡顿和启动时间,提高游戏响应速度。\n\n如果不确定,请保持开启状态。", "FsIntegrityToggleTooltip": "启动游戏时检查游戏文件的完整性,并在日志中记录损坏的文件。\n\n对性能没有影响,用于排查故障。\n\n如果不确定,请保持开启状态。", "AudioBackendTooltip": "更改音频处理引擎。\n\n推荐选择“SDL2”,另外“OpenAL”和“SoundIO”可以作为备选,选择“无”将没有声音。\n\n如果不确定,请设置为“SDL2”。", - "MemoryManagerTooltip": "更改模拟器内存映射和访问的方式,对模拟器CPU的性能影响很大。\n\n如果不确定,请设置为“跳过检查的本机映射”。", - "MemoryManagerSoftwareTooltip": "使用软件内存页管理,最准确但是速度最慢。", + "MemoryManagerTooltip": "更改模拟器内存映射和访问的方式,对模拟器 CPU 的性能影响很大。\n\n如果不确定,请设置为“跳过检查的本机映射”。", + "MemoryManagerSoftwareTooltip": "使用软件内存页进行内存地址映射,最准确但是速度最慢。", "MemoryManagerHostTooltip": "直接映射内存页到电脑内存,使得即时编译和执行的效率更高。", "MemoryManagerUnsafeTooltip": "直接映射内存页到电脑内存,并且不检查内存溢出,使得效率更高,但牺牲了安全。\n游戏程序可以访问模拟器内存的任意地址,所以不安全。\n建议此模式下只运行您信任的游戏程序。", "UseHypervisorTooltip": "使用 Hypervisor 虚拟机代替即时编译,在可用的情况下能大幅提高性能,但目前可能还不稳定。", "DRamTooltip": "模拟 Switch 开发机的内存布局。\n\n不会提高性能,某些高清纹理包或 4k 分辨率 MOD 可能需要使用此选项。\n\n如果不确定,请保持关闭状态。", "IgnoreMissingServicesTooltip": "开启后,游戏会忽略未实现的系统服务,从而继续运行。\n少部分新发布的游戏由于使用了新的未知系统服务,可能需要此选项来避免闪退。\n模拟器更新完善系统服务之后,则无需开启此选项。\n\n如果不确定,请保持关闭状态。", - "GraphicsBackendThreadingTooltip": "在第二个线程上执行图形引擎指令。\n\n加速着色器编译,减少卡顿,可以提高 GPU 的性能。\n\n如果不确定,请设置为“自动”。", - "GalThreadingTooltip": "在第二个线程上执行图形引擎指令。\n\n加速着色器编译,减少卡顿,可以提高 GPU 的性能。\n\n如果不确定,请设置为“自动”。", - "ShaderCacheToggleTooltip": "模拟器将已编译的着色器保存到硬盘,减少游戏再次渲染相同图形导致的卡顿。\n\n如果不确定,请保持开启状态。", + "GraphicsBackendThreadingTooltip": "在第二个线程上执行图形引擎指令。\n\n可以加速着色器编译,减少卡顿,提高 GPU 的性能。\n\n如果不确定,请设置为“自动”。", + "GalThreadingTooltip": "在第二个线程上执行图形引擎指令。\n\n可以加速着色器编译,减少卡顿,提高 GPU 的性能。\n\n如果不确定,请设置为“自动”。", + "ShaderCacheToggleTooltip": "模拟器将已编译的着色器保存到硬盘,可以减少游戏再次渲染相同图形导致的卡顿。\n\n如果不确定,请保持开启状态。", "ResolutionScaleTooltip": "将游戏的渲染分辨率乘以一个倍数。\n\n有些游戏可能不适用这项设置,而且即使提高了分辨率仍然看起来像素化;对于这些游戏,您可能需要找到移除抗锯齿或提高内部渲染分辨率的 MOD。当使用这些 MOD 时,建议设置为“原生”。\n\n在游戏运行时,通过点击下面的“应用”按钮可以使设置生效;你可以将设置窗口移开,并试验找到您喜欢的游戏画面效果。\n\n请记住,对于几乎所有人而言,4倍分辨率都是过度的。", "ResolutionScaleEntryTooltip": "建议设置为整数倍,带小数的分辨率缩放倍数(例如1.5),非整数倍的缩放容易导致问题或闪退。", "AnisotropyTooltip": "各向异性过滤等级,可以提高倾斜视角纹理的清晰度。\n当设置为“自动”时,使用游戏自身设定的等级。", @@ -526,22 +526,22 @@ "IconSize": "图标尺寸", "IconSizeTooltip": "更改游戏图标的显示尺寸", "MenuBarOptionsShowConsole": "显示控制台", - "ShaderCachePurgeError": "清除 {0} 的着色器缓存时出错:{1}", + "ShaderCachePurgeError": "清除 {0} 的着色器缓存文件时出错:{1}", "UserErrorNoKeys": "找不到密钥Keys", - "UserErrorNoFirmware": "找不到固件", - "UserErrorFirmwareParsingFailed": "固件解析出错", + "UserErrorNoFirmware": "未安装系统固件", + "UserErrorFirmwareParsingFailed": "固件文件解析出错", "UserErrorApplicationNotFound": "找不到游戏程序", "UserErrorUnknown": "未知错误", "UserErrorUndefined": "未定义错误", "UserErrorNoKeysDescription": "Ryujinx 模拟器找不到“prod.keys”密钥文件", - "UserErrorNoFirmwareDescription": "Ryujinx 模拟器找不到任何已安装的固件", + "UserErrorNoFirmwareDescription": "Ryujinx 模拟器未安装 Switch 系统固件", "UserErrorFirmwareParsingFailedDescription": "Ryujinx 模拟器无法解密当前固件,一般是由于使用了旧版的密钥导致的。", "UserErrorApplicationNotFoundDescription": "Ryujinx 模拟器在所选路径中找不到有效的游戏程序。", "UserErrorUnknownDescription": "出现未知错误!", "UserErrorUndefinedDescription": "出现未定义错误!此类错误不应出现,请联系开发者!", "OpenSetupGuideMessage": "打开安装指南", - "NoUpdate": "没有可用更新", - "TitleUpdateVersionLabel": "版本 {0}", + "NoUpdate": "无更新(或不加载游戏更新)", + "TitleUpdateVersionLabel": "游戏更新的版本 {0}", "RyujinxInfo": "Ryujinx - 信息", "RyujinxConfirm": "Ryujinx - 确认", "FileDialogAllTypes": "全部类型", @@ -555,7 +555,7 @@ "ControllerAppletControllers": "支持的手柄:", "ControllerAppletPlayers": "玩家:", "ControllerAppletDescription": "您当前的输入配置无效。打开设置并重新设置您的输入选项。", - "ControllerAppletDocked": "已经设置为主机模式,掌机手柄操控已经被禁用", + "ControllerAppletDocked": "已经设置为主机模式,应禁用掌机手柄操控。", "UpdaterRenaming": "正在重命名旧文件...", "UpdaterRenameFailed": "更新过程中无法重命名文件:{0}", "UpdaterAddingFiles": "安装更新中...", @@ -644,10 +644,13 @@ "Recover": "恢复", "UserProfilesRecoverHeading": "找到了这些用户的存档数据", "UserProfilesRecoverEmptyList": "没有可以恢复的用户数据", - "GraphicsAATooltip": "抗锯齿是一种图形处理技术,用于减少图像边缘的锯齿状现象,使图像更加平滑。\n\nFXAA(快速近似抗锯齿)是一种性能开销相对较小的抗锯齿方法,但可能会使得整体图像看起来有些模糊。\n\nSMAA(增强型子像素抗锯齿)则更加精细,它会尝试找到锯齿边缘并平滑它们,相比 FXAA 有更好的图像质量,但性能开销可能会稍大一些。\n\n如果开启了 FSR(FidelityFX Super Resolution,一种图像缩放过滤器)来提高性能或图像质量,不建议再启用抗锯齿,因为它们会产生不必要的图形处理开销,或者相互之间效果不协调。\n\n在游戏运行时,通过点击下面的“应用”按钮可以使设置生效;你可以将设置窗口移开,并试验找到您喜欢的游戏画面效果。\n\n如果不确定,请保持为“无”。", + "GraphicsAATooltip": "抗锯齿是一种图形处理技术,用于减少图像边缘的锯齿状现象,使图像更加平滑。\n\nFXAA(快速近似抗锯齿)是一种性能开销相对较小的抗锯齿方法,但可能会使得整体图像看起来有些模糊。\n\nSMAA(增强型子像素抗锯齿)则更加精细,它会尝试找到锯齿边缘并平滑它们,相比 FXAA 有更好的图像质量,但性能开销可能会稍大一些。\n\n如果开启了 FSR(FidelityFX Super Resolution,超级分辨率锐画技术)来提高性能或图像质量,不建议再启用抗锯齿,因为它们会产生不必要的图形处理开销,或者相互之间效果不协调。\n\n在游戏运行时,通过点击下面的“应用”按钮可以使设置生效;你可以将设置窗口移开,并试验找到您喜欢的游戏画面效果。\n\n如果不确定,请保持为“无”。", "GraphicsAALabel": "抗锯齿:", "GraphicsScalingFilterLabel": "缩放过滤:", - "GraphicsScalingFilterTooltip": "选择在分辨率缩放时将使用的缩放过滤器。\n\nBilinear(双线性过滤)对于3D游戏效果较好,是一个安全的默认选项。\n\nNearest(最近邻过滤)推荐用于像素艺术游戏。\n\nFSR 1.0 只是一个锐化过滤器,不推荐与 FXAA 或 SMAA 抗锯齿一起使用。\n\n在游戏运行时,通过点击下面的“应用”按钮可以使设置生效;你可以将设置窗口移开,并试验找到您喜欢的游戏画面效果。\n\n如果不确定,请保持为“Bilinear”。", + "GraphicsScalingFilterTooltip": "选择在分辨率缩放时将使用的缩放过滤器。\n\nBilinear(双线性过滤)对于3D游戏效果较好,是一个安全的默认选项。\n\nNearest(最近邻过滤)推荐用于像素艺术游戏。\n\nFSR(超级分辨率锐画)只是一个锐化过滤器,不推荐与 FXAA 或 SMAA 抗锯齿一起使用。\n\n在游戏运行时,通过点击下面的“应用”按钮可以使设置生效;你可以将设置窗口移开,并试验找到您喜欢的游戏画面效果。\n\n如果不确定,请保持为“Bilinear(双线性过滤)”。", + "GraphicsScalingFilterBilinear": "Bilinear(双线性过滤)", + "GraphicsScalingFilterNearest": "Nearest(最近邻过滤)", + "GraphicsScalingFilterFsr": "FSR(超级分辨率锐画技术)", "GraphicsScalingFilterLevelLabel": "等级", "GraphicsScalingFilterLevelTooltip": "设置 FSR 1.0 的锐化等级,数值越高,图像越锐利。", "SmaaLow": "SMAA 低质量", @@ -664,5 +667,7 @@ "AboutChangelogButtonTooltipMessage": "点击这里在浏览器中打开此版本的更新日志。", "SettingsTabNetworkMultiplayer": "多人联机游玩", "MultiplayerMode": "联机模式:", - "MultiplayerModeTooltip": "修改 LDN 多人联机游玩模式。\n\nLdnMitm 将修改游戏中的本地无线和本地游玩功能,使其表现得像局域网一样,允许和其他安装了 ldn_mitm 模块的 Ryujinx 模拟器和破解的任天堂 Switch 主机在同一网络下进行本地连接,实现多人联机游玩。\n\n多人联机游玩要求所有玩家必须运行相同的游戏版本(例如,任天堂明星大乱斗特别版 v13.0.1 无法与 v13.0.0 版本联机)。\n\n如果不确定,请保持为“禁用”。" + "MultiplayerModeTooltip": "修改 LDN 多人联机游玩模式。\n\nldn_mitm 联机插件将修改游戏中的本地无线和本地游玩功能,使其表现得像局域网一样,允许和其他安装了 ldn_mitm 插件的 Ryujinx 模拟器和破解的任天堂 Switch 主机在同一网络下进行本地连接,实现多人联机游玩。\n\n多人联机游玩要求所有玩家必须运行相同的游戏版本(例如,任天堂明星大乱斗特别版 v13.0.1 无法与 v13.0.0 版本联机)。\n\n如果不确定,请保持为“禁用”。", + "MultiplayerModeDisabled": "禁用", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/zh_TW.json b/src/Ryujinx/Assets/Locales/zh_TW.json index 4d3a78d1aa..301a558560 100644 --- a/src/Ryujinx/Assets/Locales/zh_TW.json +++ b/src/Ryujinx/Assets/Locales/zh_TW.json @@ -144,13 +144,13 @@ "SettingsTabGraphics": "圖形", "SettingsTabGraphicsAPI": "圖形 API", "SettingsTabGraphicsEnableShaderCache": "啟用著色器快取", - "SettingsTabGraphicsAnisotropicFiltering": "各向異性過濾:", + "SettingsTabGraphicsAnisotropicFiltering": "各向異性過濾:", "SettingsTabGraphicsAnisotropicFilteringAuto": "自動", "SettingsTabGraphicsAnisotropicFiltering2x": "2 倍", "SettingsTabGraphicsAnisotropicFiltering4x": "4 倍", "SettingsTabGraphicsAnisotropicFiltering8x": "8 倍", "SettingsTabGraphicsAnisotropicFiltering16x": "16 倍", - "SettingsTabGraphicsResolutionScale": "解析度比例:", + "SettingsTabGraphicsResolutionScale": "解析度比例:", "SettingsTabGraphicsResolutionScaleCustom": "自訂 (不建議使用)", "SettingsTabGraphicsResolutionScaleNative": "原生 (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2 倍 (1440p/2160p)", @@ -164,7 +164,7 @@ "SettingsTabGraphicsAspectRatio32x9": "32:9", "SettingsTabGraphicsAspectRatioStretch": "拉伸以適應視窗", "SettingsTabGraphicsDeveloperOptions": "開發者選項", - "SettingsTabGraphicsShaderDumpPath": "圖形著色器傾印路徑:", + "SettingsTabGraphicsShaderDumpPath": "圖形著色器傾印路徑:", "SettingsTabLogging": "日誌", "SettingsTabLoggingLogging": "日誌", "SettingsTabLoggingEnableLoggingToFile": "啟用日誌到檔案", @@ -417,7 +417,7 @@ "AboutGithubUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 的 GitHub 網頁。", "AboutDiscordUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 的 Discord 邀請連結。", "AboutTwitterUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 的 Twitter 網頁。", - "AboutRyujinxAboutTitle": "關於:", + "AboutRyujinxAboutTitle": "關於:", "AboutRyujinxAboutContent": "Ryujinx 是一款 Nintendo Switch™ 模擬器。\n請在 Patreon 上支持我們。\n關注我們的 Twitter 或 Discord 取得所有最新消息。\n對於有興趣貢獻的開發者,可以在我們的 GitHub 或 Discord 上了解更多資訊。", "AboutRyujinxMaintainersTitle": "維護者:", "AboutRyujinxMaintainersContentTooltipMessage": "在預設瀏覽器中開啟貢獻者的網頁", @@ -426,7 +426,7 @@ "AmiiboCharacterLabel": "角色", "AmiiboScanButtonLabel": "掃描", "AmiiboOptionsShowAllLabel": "顯示所有 Amiibo", - "AmiiboOptionsUsRandomTagLabel": "補釘修正: 使用隨機標記的 Uuid", + "AmiiboOptionsUsRandomTagLabel": "補釘修正:使用隨機標記的 Uuid", "DlcManagerTableHeadingEnabledLabel": "已啟用", "DlcManagerTableHeadingTitleIdLabel": "遊戲 ID", "DlcManagerTableHeadingContainerPathLabel": "容器路徑", @@ -503,7 +503,7 @@ "GridSizeTooltip": "調整網格的大小", "SettingsTabSystemSystemLanguageBrazilianPortuguese": "巴西葡萄牙文", "AboutRyujinxContributorsButtonHeader": "查看所有貢獻者", - "SettingsTabSystemAudioVolume": "音量: ", + "SettingsTabSystemAudioVolume": "音量:", "AudioVolumeTooltip": "調節音量", "SettingsTabSystemEnableInternetAccess": "訪客網際網路存取/區域網路模式", "EnableInternetAccessTooltip": "允許模擬應用程式連線網際網路。\n\n當啟用此功能且系統連線到同一接入點時,具有區域網路模式的遊戲可相互連線。這也包括真正的遊戲機。\n\n不允許連接 Nintendo 伺服器。可能會導致某些嘗試連線網際網路的遊戲崩潰。\n\n如果不確定,請保持關閉狀態。", @@ -564,7 +564,7 @@ "Game": "遊戲", "Docked": "底座模式", "Handheld": "手提模式", - "ConnectionError": "連接錯誤。", + "ConnectionError": "連線錯誤。", "AboutPageDeveloperListMore": "{0} 等人...", "ApiError": "API 錯誤。", "LoadingHeading": "正在載入 {0}", @@ -619,7 +619,7 @@ "SettingsTabGraphicsBackendTooltip": "選擇模擬器將使用的圖形後端。\n\n只要驅動程式是最新的,Vulkan 對所有現代顯示卡來說都更好用。Vulkan 還能在所有 GPU 廠商上實現更快的著色器編譯 (減少卡頓)。\n\nOpenGL 在舊式 Nvidia GPU、Linux 上的舊式 AMD GPU 或 VRAM 較低的 GPU 上可能會取得更好的效果,不過著色器編譯的卡頓會更嚴重。\n\n如果不確定,請設定為 Vulkan。如果您的 GPU 使用最新的圖形驅動程式也不支援 Vulkan,請設定為 OpenGL。", "SettingsEnableTextureRecompression": "開啟材質重新壓縮", "SettingsEnableTextureRecompressionTooltip": "壓縮 ASTC 紋理,以減少 VRAM 占用。\n\n使用這種紋理格式的遊戲包括 Astral Chain、Bayonetta 3、Fire Emblem Engage、Metroid Prime Remastered、Super Mario Bros. Wonder 和 The Legend of Zelda: Tears of the Kingdom。\n\n使用 4GB 或更低 VRAM 的顯示卡在執行這些遊戲時可能會崩潰。\n\n只有在上述遊戲的 VRAM 即將耗盡時才啟用。如果不確定,請保持關閉狀態。", - "SettingsTabGraphicsPreferredGpu": "首選 GPU", + "SettingsTabGraphicsPreferredGpu": "優先選取的 GPU", "SettingsTabGraphicsPreferredGpuTooltip": "選擇將與 Vulkan 圖形後端一起使用的顯示卡。\n\n不會影響 OpenGL 將使用的 GPU。\n\n如果不確定,請設定為標記為「dGPU」的 GPU。如果沒有,則保持原狀。", "SettingsAppRequiredRestartMessage": "需要重新啟動 Ryujinx", "SettingsGpuBackendRestartMessage": "圖形後端或 GPU 設定已修改。這需要重新啟動才能套用。", @@ -648,6 +648,9 @@ "GraphicsAALabel": "反鋸齒:", "GraphicsScalingFilterLabel": "縮放過濾器:", "GraphicsScalingFilterTooltip": "選擇使用解析度縮放時套用的縮放過濾器。\n\nBilinear (雙線性) 濾鏡適用於 3D 遊戲,是一個安全的預設選項。\n\n建議像素美術遊戲使用 Nearest (最近性) 濾鏡。\n\nFSR 1.0 只是一個銳化濾鏡,不建議與 FXAA 或 SMAA 一起使用。\n\n此選項可在遊戲執行時透過點選下方的「套用」進行變更;您只需將設定視窗移到一旁,然後進行試驗,直到找到您喜歡的遊戲效果。\n\n如果不確定,請保持 Bilinear (雙線) 狀態。", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "日誌等級", "GraphicsScalingFilterLevelTooltip": "設定 FSR 1.0 銳化等級。越高越清晰。", "SmaaLow": "低階 SMAA", @@ -664,5 +667,7 @@ "AboutChangelogButtonTooltipMessage": "在預設瀏覽器中開啟此版本的更新日誌。", "SettingsTabNetworkMultiplayer": "多人遊戲", "MultiplayerMode": "模式:", - "MultiplayerModeTooltip": "變更 LDN 多人遊戲模式。\n\nLdnMitm 將修改遊戲中的本機無線/本機遊戲功能,使其如同區域網路一樣執行,允許與其他安裝了 ldn_mitm 模組的 Ryujinx 實例和已破解的 Nintendo Switch 遊戲機進行本機同網路連線。\n\n多人遊戲要求所有玩家使用相同的遊戲版本 (例如,Super Smash Bros. Ultimate v13.0.1 無法連接 v13.0.0)。\n\n如果不確定,請保持 Disabled (停用) 狀態。" + "MultiplayerModeTooltip": "變更 LDN 多人遊戲模式。\n\nLdnMitm 將修改遊戲中的本機無線/本機遊戲功能,使其如同區域網路一樣執行,允許與其他安裝了 ldn_mitm 模組的 Ryujinx 實例和已破解的 Nintendo Switch 遊戲機進行本機同網路連線。\n\n多人遊戲要求所有玩家使用相同的遊戲版本 (例如,Super Smash Bros. Ultimate v13.0.1 無法連接 v13.0.0)。\n\n如果不確定,請保持 Disabled (停用) 狀態。", + "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeLdnMitm": "ldn_mitm" } From 8e74fa34560c4a8c3de234eb3488e1d0fb6f8f6c Mon Sep 17 00:00:00 2001 From: gdkchan Date: Wed, 3 Apr 2024 21:30:46 -0300 Subject: [PATCH 22/87] Stop clearing Modified flag on DiscardData (#6591) --- src/Ryujinx.Graphics.Gpu/Image/Texture.cs | 2 +- src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs | 2 +- src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs index f1615b388b..7a043b2b7c 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -573,7 +573,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// /// Discards all data for this texture. - /// This clears all dirty flags, modified flags, and pending copies from other textures. + /// This clears all dirty flags and pending copies from other textures. /// It should be used if the texture data will be fully overwritten by the next use. /// public void DiscardData() diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs index 34a9a9c75f..9785839426 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs @@ -282,7 +282,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// /// Discards all data for a given texture. - /// This clears all dirty flags, modified flags, and pending copies from other textures. + /// This clears all dirty flags and pending copies from other textures. /// /// The texture being discarded public void DiscardData(Texture texture) diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs index d345ec2dba..0af6b7ca83 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs @@ -182,11 +182,10 @@ namespace Ryujinx.Graphics.Gpu.Image /// /// Discards all data for this handle. - /// This clears all dirty flags, modified flags, and pending copies from other handles. + /// This clears all dirty flags and pending copies from other handles. /// public void DiscardData() { - Modified = false; DeferredCopy = null; foreach (RegionHandle handle in Handles) From 5def0429f82c795d820b2307a0301b78dfb1e6b7 Mon Sep 17 00:00:00 2001 From: jhorv <38920027+jhorv@users.noreply.github.com> Date: Thu, 4 Apr 2024 21:23:03 -0400 Subject: [PATCH 23/87] Add support to IVirtualMemoryManager for zero-copy reads (#6251) * - WritableRegion: enable wrapping IMemoryOwner - IVirtualMemoryManager impls of GetWritableRegion() use pooled memory when region is non-contiguous. - IVirtualMemoryManager: add GetReadOnlySequence() and impls - ByteMemoryPool: add new method RentCopy() - ByteMemoryPool: make class static, remove ctor and singleton field from earlier impl * - BytesReadOnlySequenceSegment: move from Ryujinx.Common.Memory to Ryujinx.Memory - BytesReadOnlySequenceSegment: add IsContiguousWith() and Replace() methods - VirtualMemoryManagerBase: - remove generic type parameters, instead use ulong for virtual addresses and nuint for host/physical addresses - implement IWritableBlock - add virtual GetReadOnlySequence() with coalescing of contiguous segments - add virtual GetSpan() - add virtual GetWritableRegion() - add abstract IsMapped() - add virtual MapForeign(ulong, nuint, ulong) - add virtual Read() - add virtual Read(ulong, Span) - add virtual ReadTracked() - add virtual SignalMemoryTracking() - add virtual Write() - add virtual Write() - add virtual WriteUntracked() - add virtual WriteWithRedundancyCheck() - VirtualMemoryManagerRefCountedBase: remove generic type parameters - AddressSpaceManager: remove redundant methods, add required overrides - HvMemoryManager: remove redundant methods, add required overrides, add overrides for _invalidAccessHandler handling - MemoryManager: remove redundant methods, add required overrides, add overrides for _invalidAccessHandler handling - MemoryManagerHostMapped: remove redundant methods, add required overrides, add overrides for _invalidAccessHandler handling - NativeMemoryManager: add get properties for Pointer and Length - throughout: removed invalid comments * make HvMemoryManager class sealed * remove unused method * adjust MemoryManagerHostTracked * let MemoryManagerHostTracked override WriteImpl() --- .../ByteMemoryPool.ByteMemoryPoolBuffer.cs | 2 +- src/Ryujinx.Common/Memory/ByteMemoryPool.cs | 32 +- src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs | 249 ++----------- src/Ryujinx.Cpu/Jit/MemoryManager.cs | 266 +++---------- .../Jit/MemoryManagerHostMapped.cs | 89 ++--- .../Jit/MemoryManagerHostTracked.cs | 197 ++++------ .../VirtualMemoryManagerRefCountedBase.cs | 5 +- src/Ryujinx.Memory/AddressSpaceManager.cs | 176 +-------- .../BytesReadOnlySequenceSegment.cs | 60 +++ src/Ryujinx.Memory/IVirtualMemoryManager.cs | 10 + src/Ryujinx.Memory/NativeMemoryManager.cs | 4 + .../VirtualMemoryManagerBase.cs | 348 +++++++++++++++++- src/Ryujinx.Memory/WritableRegion.cs | 10 + .../MockVirtualMemoryManager.cs | 6 + 14 files changed, 635 insertions(+), 819 deletions(-) create mode 100644 src/Ryujinx.Memory/BytesReadOnlySequenceSegment.cs diff --git a/src/Ryujinx.Common/Memory/ByteMemoryPool.ByteMemoryPoolBuffer.cs b/src/Ryujinx.Common/Memory/ByteMemoryPool.ByteMemoryPoolBuffer.cs index df3f8dc93e..05fb29ac71 100644 --- a/src/Ryujinx.Common/Memory/ByteMemoryPool.ByteMemoryPoolBuffer.cs +++ b/src/Ryujinx.Common/Memory/ByteMemoryPool.ByteMemoryPoolBuffer.cs @@ -4,7 +4,7 @@ using System.Threading; namespace Ryujinx.Common.Memory { - public sealed partial class ByteMemoryPool + public partial class ByteMemoryPool { /// /// Represents a that wraps an array rented from diff --git a/src/Ryujinx.Common/Memory/ByteMemoryPool.cs b/src/Ryujinx.Common/Memory/ByteMemoryPool.cs index 071f56b136..6fd6a98aa7 100644 --- a/src/Ryujinx.Common/Memory/ByteMemoryPool.cs +++ b/src/Ryujinx.Common/Memory/ByteMemoryPool.cs @@ -6,24 +6,8 @@ namespace Ryujinx.Common.Memory /// /// Provides a pool of re-usable byte array instances. /// - public sealed partial class ByteMemoryPool + public static partial class ByteMemoryPool { - private static readonly ByteMemoryPool _shared = new(); - - /// - /// Constructs a instance. Private to force access through - /// the instance. - /// - private ByteMemoryPool() - { - // No implementation - } - - /// - /// Retrieves a shared instance. - /// - public static ByteMemoryPool Shared => _shared; - /// /// Returns the maximum buffer size supported by this pool. /// @@ -95,6 +79,20 @@ namespace Ryujinx.Common.Memory return buffer; } + /// + /// Copies into a newly rented byte memory buffer. + /// + /// The byte buffer to copy + /// A wrapping the rented memory with copied to it + public static IMemoryOwner RentCopy(ReadOnlySpan buffer) + { + var copy = RentImpl(buffer.Length); + + buffer.CopyTo(copy.Memory.Span); + + return copy; + } + private static ByteMemoryPoolBuffer RentImpl(int length) { if ((uint)length > Array.MaxLength) diff --git a/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs b/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs index 80f7c8a1f8..0c2e5f33a0 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs @@ -3,10 +3,10 @@ using Ryujinx.Memory; using Ryujinx.Memory.Range; using Ryujinx.Memory.Tracking; using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Runtime.Versioning; namespace Ryujinx.Cpu.AppleHv @@ -15,7 +15,7 @@ namespace Ryujinx.Cpu.AppleHv /// Represents a CPU memory manager which maps guest virtual memory directly onto the Hypervisor page table. /// [SupportedOSPlatform("macos")] - public class HvMemoryManager : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock + public sealed class HvMemoryManager : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked { private readonly InvalidAccessHandler _invalidAccessHandler; @@ -96,12 +96,6 @@ namespace Ryujinx.Cpu.AppleHv } } - /// - public void MapForeign(ulong va, nuint hostPointer, ulong size) - { - throw new NotSupportedException(); - } - /// public void Unmap(ulong va, ulong size) { @@ -126,20 +120,11 @@ namespace Ryujinx.Cpu.AppleHv } } - /// - public T Read(ulong va) where T : unmanaged - { - return MemoryMarshal.Cast(GetSpan(va, Unsafe.SizeOf()))[0]; - } - - /// - public T ReadTracked(ulong va) where T : unmanaged + public override T ReadTracked(ulong va) { try { - SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), false); - - return Read(va); + return base.ReadTracked(va); } catch (InvalidMemoryRegionException) { @@ -152,7 +137,6 @@ namespace Ryujinx.Cpu.AppleHv } } - /// public override void Read(ulong va, Span data) { try @@ -168,101 +152,11 @@ namespace Ryujinx.Cpu.AppleHv } } - /// - public void Write(ulong va, T value) where T : unmanaged - { - Write(va, MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1))); - } - - /// - public void Write(ulong va, ReadOnlySpan data) - { - if (data.Length == 0) - { - return; - } - - SignalMemoryTracking(va, (ulong)data.Length, true); - - WriteImpl(va, data); - } - - /// - public void WriteUntracked(ulong va, ReadOnlySpan data) - { - if (data.Length == 0) - { - return; - } - - WriteImpl(va, data); - } - - /// - public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data) - { - if (data.Length == 0) - { - return false; - } - - SignalMemoryTracking(va, (ulong)data.Length, false); - - if (IsContiguousAndMapped(va, data.Length)) - { - var target = _backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length); - - bool changed = !data.SequenceEqual(target); - - if (changed) - { - data.CopyTo(target); - } - - return changed; - } - else - { - WriteImpl(va, data); - - return true; - } - } - - private void WriteImpl(ulong va, ReadOnlySpan data) + public override void Write(ulong va, ReadOnlySpan data) { try { - AssertValidAddressAndSize(va, (ulong)data.Length); - - if (IsContiguousAndMapped(va, data.Length)) - { - data.CopyTo(_backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length)); - } - else - { - int offset = 0, size; - - if ((va & PageMask) != 0) - { - ulong pa = GetPhysicalAddressChecked(va); - - size = Math.Min(data.Length, PageSize - (int)(va & PageMask)); - - data[..size].CopyTo(_backingMemory.GetSpan(pa, size)); - - offset += size; - } - - for (; offset < data.Length; offset += size) - { - ulong pa = GetPhysicalAddressChecked(va + (ulong)offset); - - size = Math.Min(data.Length - offset, PageSize); - - data.Slice(offset, size).CopyTo(_backingMemory.GetSpan(pa, size)); - } - } + base.Write(va, data); } catch (InvalidMemoryRegionException) { @@ -273,61 +167,38 @@ namespace Ryujinx.Cpu.AppleHv } } - /// - public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) + public override void WriteUntracked(ulong va, ReadOnlySpan data) { - if (size == 0) + try { - return ReadOnlySpan.Empty; + base.WriteUntracked(va, data); } - - if (tracked) + catch (InvalidMemoryRegionException) { - SignalMemoryTracking(va, (ulong)size, false); - } - - if (IsContiguousAndMapped(va, size)) - { - return _backingMemory.GetSpan(GetPhysicalAddressInternal(va), size); - } - else - { - Span data = new byte[size]; - - base.Read(va, data); - - return data; + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } } } - /// - public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) + public override ReadOnlySequence GetReadOnlySequence(ulong va, int size, bool tracked = false) { - if (size == 0) + try { - return new WritableRegion(null, va, Memory.Empty); + return base.GetReadOnlySequence(va, size, tracked); } - - if (tracked) + catch (InvalidMemoryRegionException) { - SignalMemoryTracking(va, (ulong)size, true); - } + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } - if (IsContiguousAndMapped(va, size)) - { - return new WritableRegion(null, va, _backingMemory.GetMemory(GetPhysicalAddressInternal(va), size)); - } - else - { - Memory memory = new byte[size]; - - base.Read(va, memory.Span); - - return new WritableRegion(this, va, memory); + return ReadOnlySequence.Empty; } } - /// public ref T GetRef(ulong va) where T : unmanaged { if (!IsContiguous(va, Unsafe.SizeOf())) @@ -340,9 +211,8 @@ namespace Ryujinx.Cpu.AppleHv return ref _backingMemory.GetRef(GetPhysicalAddressChecked(va)); } - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsMapped(ulong va) + public override bool IsMapped(ulong va) { return ValidateAddress(va) && _pages.IsMapped(va); } @@ -355,39 +225,6 @@ namespace Ryujinx.Cpu.AppleHv return _pages.IsRangeMapped(va, size); } - private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool IsContiguousAndMapped(ulong va, int size) => IsContiguous(va, size) && IsMapped(va); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool IsContiguous(ulong va, int size) - { - if (!ValidateAddress(va) || !ValidateAddressAndSize(va, (ulong)size)) - { - return false; - } - - int pages = GetPagesCount(va, (uint)size, out va); - - for (int page = 0; page < pages - 1; page++) - { - if (!ValidateAddress(va + PageSize)) - { - return false; - } - - if (GetPhysicalAddressInternal(va) + PageSize != GetPhysicalAddressInternal(va + PageSize)) - { - return false; - } - - va += PageSize; - } - - return true; - } - /// public IEnumerable GetHostRegions(ulong va, ulong size) { @@ -464,11 +301,10 @@ namespace Ryujinx.Cpu.AppleHv return regions; } - /// /// /// This function also validates that the given range is both valid and mapped, and will throw if it is not. /// - public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) + public override void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) { AssertValidAddressAndSize(va, size); @@ -481,24 +317,6 @@ namespace Ryujinx.Cpu.AppleHv _pages.SignalMemoryTracking(Tracking, va, size, write, exemptId); } - /// - /// Computes the number of pages in a virtual address range. - /// - /// Virtual address of the range - /// Size of the range - /// The virtual address of the beginning of the first page - /// This function does not differentiate between allocated and unallocated pages. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetPagesCount(ulong va, ulong size, out ulong startVa) - { - // WARNING: Always check if ulong does not overflow during the operations. - startVa = va & ~(ulong)PageMask; - ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask; - - return (int)(vaSpan / PageSize); - } - - /// public void Reprotect(ulong va, ulong size, MemoryPermission protection) { // TODO @@ -535,7 +353,7 @@ namespace Ryujinx.Cpu.AppleHv return Tracking.BeginSmartGranularTracking(address, size, granularity, id); } - private ulong GetPhysicalAddressChecked(ulong va) + private nuint GetPhysicalAddressChecked(ulong va) { if (!IsMapped(va)) { @@ -545,9 +363,9 @@ namespace Ryujinx.Cpu.AppleHv return GetPhysicalAddressInternal(va); } - private ulong GetPhysicalAddressInternal(ulong va) + private nuint GetPhysicalAddressInternal(ulong va) { - return _pageTable.Read(va) + (va & PageMask); + return (nuint)(_pageTable.Read(va) + (va & PageMask)); } /// @@ -558,10 +376,17 @@ namespace Ryujinx.Cpu.AppleHv _addressSpace.Dispose(); } - protected override Span GetPhysicalAddressSpan(ulong pa, int size) + protected override Memory GetPhysicalAddressMemory(nuint pa, int size) + => _backingMemory.GetMemory(pa, size); + + protected override Span GetPhysicalAddressSpan(nuint pa, int size) => _backingMemory.GetSpan(pa, size); - protected override ulong TranslateVirtualAddressForRead(ulong va) + protected override nuint TranslateVirtualAddressChecked(ulong va) => GetPhysicalAddressChecked(va); + + protected override nuint TranslateVirtualAddressUnchecked(ulong va) + => GetPhysicalAddressInternal(va); + } } diff --git a/src/Ryujinx.Cpu/Jit/MemoryManager.cs b/src/Ryujinx.Cpu/Jit/MemoryManager.cs index c87c8b8cc5..dfa5b93539 100644 --- a/src/Ryujinx.Cpu/Jit/MemoryManager.cs +++ b/src/Ryujinx.Cpu/Jit/MemoryManager.cs @@ -3,6 +3,7 @@ using Ryujinx.Memory; using Ryujinx.Memory.Range; using Ryujinx.Memory.Tracking; using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; @@ -14,7 +15,7 @@ namespace Ryujinx.Cpu.Jit /// /// Represents a CPU memory manager. /// - public sealed class MemoryManager : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock + public sealed class MemoryManager : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked { private const int PteSize = 8; @@ -97,12 +98,6 @@ namespace Ryujinx.Cpu.Jit Tracking.Map(oVa, size); } - /// - public void MapForeign(ulong va, nuint hostPointer, ulong size) - { - throw new NotSupportedException(); - } - /// public void Unmap(ulong va, ulong size) { @@ -128,20 +123,11 @@ namespace Ryujinx.Cpu.Jit } } - /// - public T Read(ulong va) where T : unmanaged - { - return MemoryMarshal.Cast(GetSpan(va, Unsafe.SizeOf()))[0]; - } - - /// - public T ReadTracked(ulong va) where T : unmanaged + public override T ReadTracked(ulong va) { try { - SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), false); - - return Read(va); + return base.ReadTracked(va); } catch (InvalidMemoryRegionException) { @@ -190,117 +176,11 @@ namespace Ryujinx.Cpu.Jit } } - /// - public void Write(ulong va, T value) where T : unmanaged - { - Write(va, MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1))); - } - - /// - public void Write(ulong va, ReadOnlySpan data) - { - if (data.Length == 0) - { - return; - } - - SignalMemoryTracking(va, (ulong)data.Length, true); - - WriteImpl(va, data); - } - - /// - public void WriteGuest(ulong va, T value) where T : unmanaged - { - Span data = MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1)); - - SignalMemoryTrackingImpl(va, (ulong)data.Length, true, true); - - WriteImpl(va, data); - } - - /// - public void WriteUntracked(ulong va, ReadOnlySpan data) - { - if (data.Length == 0) - { - return; - } - - WriteImpl(va, data); - } - - /// - public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data) - { - if (data.Length == 0) - { - return false; - } - - SignalMemoryTracking(va, (ulong)data.Length, false); - - if (IsContiguousAndMapped(va, data.Length)) - { - var target = _backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length); - - bool changed = !data.SequenceEqual(target); - - if (changed) - { - data.CopyTo(target); - } - - return changed; - } - else - { - WriteImpl(va, data); - - return true; - } - } - - /// - /// Writes data to CPU mapped memory. - /// - /// Virtual address to write the data into - /// Data to be written - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteImpl(ulong va, ReadOnlySpan data) + public override void Write(ulong va, ReadOnlySpan data) { try { - AssertValidAddressAndSize(va, (ulong)data.Length); - - if (IsContiguousAndMapped(va, data.Length)) - { - data.CopyTo(_backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length)); - } - else - { - int offset = 0, size; - - if ((va & PageMask) != 0) - { - ulong pa = GetPhysicalAddressInternal(va); - - size = Math.Min(data.Length, PageSize - (int)(va & PageMask)); - - data[..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(_backingMemory.GetSpan(pa, size)); - } - } + base.Write(va, data); } catch (InvalidMemoryRegionException) { @@ -312,60 +192,47 @@ namespace Ryujinx.Cpu.Jit } /// - public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) + public void WriteGuest(ulong va, T value) where T : unmanaged { - if (size == 0) + Span data = MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1)); + + SignalMemoryTrackingImpl(va, (ulong)data.Length, true, true); + + Write(va, data); + } + + public override void WriteUntracked(ulong va, ReadOnlySpan data) + { + try { - return ReadOnlySpan.Empty; + base.WriteUntracked(va, data); } - - if (tracked) + catch (InvalidMemoryRegionException) { - SignalMemoryTracking(va, (ulong)size, false); - } - - if (IsContiguousAndMapped(va, size)) - { - return _backingMemory.GetSpan(GetPhysicalAddressInternal(va), size); - } - else - { - Span data = new byte[size]; - - base.Read(va, data); - - return data; + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } } } - /// - public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) + public override ReadOnlySequence GetReadOnlySequence(ulong va, int size, bool tracked = false) { - if (size == 0) + try { - return new WritableRegion(null, va, Memory.Empty); + return base.GetReadOnlySequence(va, size, tracked); } - - if (IsContiguousAndMapped(va, size)) + catch (InvalidMemoryRegionException) { - if (tracked) + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) { - SignalMemoryTracking(va, (ulong)size, true); + throw; } - return new WritableRegion(null, va, _backingMemory.GetMemory(GetPhysicalAddressInternal(va), size)); - } - else - { - Memory memory = new byte[size]; - - GetSpan(va, size).CopyTo(memory.Span); - - return new WritableRegion(this, va, memory, tracked); + return ReadOnlySequence.Empty; } } - /// public ref T GetRef(ulong va) where T : unmanaged { if (!IsContiguous(va, Unsafe.SizeOf())) @@ -378,56 +245,6 @@ namespace Ryujinx.Cpu.Jit 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 static int GetPagesCount(ulong va, uint size, out ulong startVa) - { - // WARNING: Always check if ulong does not overflow during the operations. - startVa = va & ~(ulong)PageMask; - ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask; - - return (int)(vaSpan / PageSize); - } - - private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool IsContiguousAndMapped(ulong va, int size) => IsContiguous(va, size) && IsMapped(va); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool IsContiguous(ulong va, int size) - { - if (!ValidateAddress(va) || !ValidateAddressAndSize(va, (ulong)size)) - { - return false; - } - - int pages = GetPagesCount(va, (uint)size, out va); - - for (int page = 0; page < pages - 1; page++) - { - if (!ValidateAddress(va + PageSize)) - { - return false; - } - - if (GetPhysicalAddressInternal(va) + PageSize != GetPhysicalAddressInternal(va + PageSize)) - { - return false; - } - - va += PageSize; - } - - return true; - } - /// public IEnumerable GetHostRegions(ulong va, ulong size) { @@ -532,9 +349,8 @@ namespace Ryujinx.Cpu.Jit return true; } - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsMapped(ulong va) + public override bool IsMapped(ulong va) { if (!ValidateAddress(va)) { @@ -544,9 +360,9 @@ namespace Ryujinx.Cpu.Jit return _pageTable.Read((va / PageSize) * PteSize) != 0; } - private ulong GetPhysicalAddressInternal(ulong va) + private nuint GetPhysicalAddressInternal(ulong va) { - return PteToPa(_pageTable.Read((va / PageSize) * PteSize) & ~(0xffffUL << 48)) + (va & PageMask); + return (nuint)(PteToPa(_pageTable.Read((va / PageSize) * PteSize) & ~(0xffffUL << 48)) + (va & PageMask)); } /// @@ -643,9 +459,7 @@ namespace Ryujinx.Cpu.Jit { ref long pageRef = ref _pageTable.GetRef(pageStart * PteSize); - long pte; - - pte = Volatile.Read(ref pageRef); + long pte = Volatile.Read(ref pageRef); if ((pte & tag) != 0) { @@ -663,7 +477,7 @@ namespace Ryujinx.Cpu.Jit } /// - public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) + public override void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) { SignalMemoryTrackingImpl(va, size, write, false, precise, exemptId); } @@ -683,10 +497,16 @@ namespace Ryujinx.Cpu.Jit /// protected override void Destroy() => _pageTable.Dispose(); - protected override Span GetPhysicalAddressSpan(ulong pa, int size) + protected override Memory GetPhysicalAddressMemory(nuint pa, int size) + => _backingMemory.GetMemory(pa, size); + + protected override Span GetPhysicalAddressSpan(nuint pa, int size) => _backingMemory.GetSpan(pa, size); - protected override ulong TranslateVirtualAddressForRead(ulong va) + protected override nuint TranslateVirtualAddressChecked(ulong va) + => GetPhysicalAddressInternal(va); + + protected override nuint TranslateVirtualAddressUnchecked(ulong va) => GetPhysicalAddressInternal(va); } } diff --git a/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs b/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs index f410d02e96..c60ab6b246 100644 --- a/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs +++ b/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs @@ -3,6 +3,7 @@ using Ryujinx.Memory; using Ryujinx.Memory.Range; using Ryujinx.Memory.Tracking; using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; @@ -12,7 +13,7 @@ namespace Ryujinx.Cpu.Jit /// /// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region. /// - public sealed class MemoryManagerHostMapped : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock + public sealed class MemoryManagerHostMapped : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked { private readonly InvalidAccessHandler _invalidAccessHandler; private readonly bool _unsafeMode; @@ -96,12 +97,6 @@ namespace Ryujinx.Cpu.Jit Tracking.Map(va, size); } - /// - public void MapForeign(ulong va, nuint hostPointer, ulong size) - { - throw new NotSupportedException(); - } - /// public void Unmap(ulong va, ulong size) { @@ -138,8 +133,7 @@ namespace Ryujinx.Cpu.Jit } } - /// - public T Read(ulong va) where T : unmanaged + public override T Read(ulong va) { try { @@ -158,14 +152,11 @@ namespace Ryujinx.Cpu.Jit } } - /// - public T ReadTracked(ulong va) where T : unmanaged + public override T ReadTracked(ulong va) { try { - SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), false); - - return Read(va); + return base.ReadTracked(va); } catch (InvalidMemoryRegionException) { @@ -178,7 +169,6 @@ namespace Ryujinx.Cpu.Jit } } - /// public override void Read(ulong va, Span data) { try @@ -196,9 +186,7 @@ namespace Ryujinx.Cpu.Jit } } - - /// - public void Write(ulong va, T value) where T : unmanaged + public override void Write(ulong va, T value) { try { @@ -215,8 +203,7 @@ namespace Ryujinx.Cpu.Jit } } - /// - public void Write(ulong va, ReadOnlySpan data) + public override void Write(ulong va, ReadOnlySpan data) { try { @@ -233,8 +220,7 @@ namespace Ryujinx.Cpu.Jit } } - /// - public void WriteUntracked(ulong va, ReadOnlySpan data) + public override void WriteUntracked(ulong va, ReadOnlySpan data) { try { @@ -251,8 +237,7 @@ namespace Ryujinx.Cpu.Jit } } - /// - public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data) + public override bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data) { try { @@ -279,8 +264,21 @@ namespace Ryujinx.Cpu.Jit } } - /// - public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) + public override ReadOnlySequence GetReadOnlySequence(ulong va, int size, bool tracked = false) + { + if (tracked) + { + SignalMemoryTracking(va, (ulong)size, write: false); + } + else + { + AssertMapped(va, (ulong)size); + } + + return new ReadOnlySequence(_addressSpace.Mirror.GetMemory(va, size)); + } + + public override ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) { if (tracked) { @@ -294,8 +292,7 @@ namespace Ryujinx.Cpu.Jit return _addressSpace.Mirror.GetSpan(va, size); } - /// - public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) + public override WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) { if (tracked) { @@ -309,7 +306,6 @@ namespace Ryujinx.Cpu.Jit return _addressSpace.Mirror.GetWritableRegion(va, size); } - /// public ref T GetRef(ulong va) where T : unmanaged { SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), true); @@ -317,9 +313,8 @@ namespace Ryujinx.Cpu.Jit return ref _addressSpace.Mirror.GetRef(va); } - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsMapped(ulong va) + public override bool IsMapped(ulong va) { return ValidateAddress(va) && _pages.IsMapped(va); } @@ -390,11 +385,10 @@ namespace Ryujinx.Cpu.Jit return _pageTable.Read(va) + (va & PageMask); } - /// /// /// This function also validates that the given range is both valid and mapped, and will throw if it is not. /// - public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) + public override void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) { AssertValidAddressAndSize(va, size); @@ -407,23 +401,6 @@ namespace Ryujinx.Cpu.Jit _pages.SignalMemoryTracking(Tracking, va, size, write, exemptId); } - /// - /// Computes the number of pages in a virtual address range. - /// - /// Virtual address of the range - /// Size of the range - /// The virtual address of the beginning of the first page - /// This function does not differentiate between allocated and unallocated pages. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetPagesCount(ulong va, ulong size, out ulong startVa) - { - // WARNING: Always check if ulong does not overflow during the operations. - startVa = va & ~(ulong)PageMask; - ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask; - - return (int)(vaSpan / PageSize); - } - /// public void Reprotect(ulong va, ulong size, MemoryPermission protection) { @@ -470,10 +447,16 @@ namespace Ryujinx.Cpu.Jit _memoryEh.Dispose(); } - protected override Span GetPhysicalAddressSpan(ulong pa, int size) + protected override Memory GetPhysicalAddressMemory(nuint pa, int size) + => _addressSpace.Mirror.GetMemory(pa, size); + + protected override Span GetPhysicalAddressSpan(nuint pa, int size) => _addressSpace.Mirror.GetSpan(pa, size); - protected override ulong TranslateVirtualAddressForRead(ulong va) - => va; + protected override nuint TranslateVirtualAddressChecked(ulong va) + => (nuint)GetPhysicalAddressChecked(va); + + protected override nuint TranslateVirtualAddressUnchecked(ulong va) + => (nuint)GetPhysicalAddressInternal(va); } } diff --git a/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs b/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs index 18404bcc74..b2964cd29c 100644 --- a/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs +++ b/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs @@ -8,14 +8,13 @@ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; namespace Ryujinx.Cpu.Jit { /// /// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region. /// - public sealed class MemoryManagerHostTracked : VirtualMemoryManagerRefCountedBase, IWritableBlock, IMemoryManager, IVirtualMemoryManagerTracked + public sealed class MemoryManagerHostTracked : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked { private readonly InvalidAccessHandler _invalidAccessHandler; private readonly bool _unsafeMode; @@ -100,12 +99,6 @@ namespace Ryujinx.Cpu.Jit Tracking.Map(va, size); } - /// - public void MapForeign(ulong va, nuint hostPointer, ulong size) - { - throw new NotSupportedException(); - } - /// public void Unmap(ulong va, ulong size) { @@ -120,18 +113,11 @@ namespace Ryujinx.Cpu.Jit _nativePageTable.Unmap(va, size); } - public T Read(ulong va) where T : unmanaged - { - return MemoryMarshal.Cast(GetSpan(va, Unsafe.SizeOf()))[0]; - } - - public T ReadTracked(ulong va) where T : unmanaged + public override T ReadTracked(ulong va) { try { - SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), false); - - return Read(va); + return base.ReadTracked(va); } catch (InvalidMemoryRegionException) { @@ -145,38 +131,39 @@ namespace Ryujinx.Cpu.Jit } public override void Read(ulong va, Span data) - { - ReadImpl(va, data); - } - - public void Write(ulong va, T value) where T : unmanaged - { - Write(va, MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1))); - } - - public void Write(ulong va, ReadOnlySpan data) { if (data.Length == 0) { return; } - SignalMemoryTracking(va, (ulong)data.Length, true); - - WriteImpl(va, data); - } - - public void WriteUntracked(ulong va, ReadOnlySpan data) - { - if (data.Length == 0) + try { - return; - } + AssertValidAddressAndSize(va, (ulong)data.Length); - WriteImpl(va, data); + ulong endVa = va + (ulong)data.Length; + int offset = 0; + + while (va < endVa) + { + (MemoryBlock memory, ulong rangeOffset, ulong copySize) = GetMemoryOffsetAndSize(va, (ulong)(data.Length - offset)); + + memory.GetSpan(rangeOffset, (int)copySize).CopyTo(data.Slice(offset, (int)copySize)); + + va += copySize; + offset += (int)copySize; + } + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + } } - public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data) + public override bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data) { if (data.Length == 0) { @@ -206,35 +193,7 @@ namespace Ryujinx.Cpu.Jit } } - private void WriteImpl(ulong va, ReadOnlySpan data) - { - try - { - AssertValidAddressAndSize(va, (ulong)data.Length); - - ulong endVa = va + (ulong)data.Length; - int offset = 0; - - while (va < endVa) - { - (MemoryBlock memory, ulong rangeOffset, ulong copySize) = GetMemoryOffsetAndSize(va, (ulong)(data.Length - offset)); - - data.Slice(offset, (int)copySize).CopyTo(memory.GetSpan(rangeOffset, (int)copySize)); - - va += copySize; - offset += (int)copySize; - } - } - catch (InvalidMemoryRegionException) - { - if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) - { - throw; - } - } - } - - public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) + public override ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) { if (size == 0) { @@ -254,13 +213,13 @@ namespace Ryujinx.Cpu.Jit { Span data = new byte[size]; - ReadImpl(va, data); + Read(va, data); return data; } } - public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) + public override WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) { if (size == 0) { @@ -280,7 +239,7 @@ namespace Ryujinx.Cpu.Jit { Memory memory = new byte[size]; - ReadImpl(va, memory.Span); + Read(va, memory.Span); return new WritableRegion(this, va, memory); } @@ -299,7 +258,7 @@ namespace Ryujinx.Cpu.Jit } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsMapped(ulong va) + public override bool IsMapped(ulong va) { return ValidateAddress(va) && _pages.IsMapped(va); } @@ -311,8 +270,6 @@ namespace Ryujinx.Cpu.Jit return _pages.IsRangeMapped(va, size); } - private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException(); - private bool TryGetVirtualContiguous(ulong va, int size, out MemoryBlock memory, out ulong offset) { if (_addressSpace.HasAnyPrivateAllocation(va, (ulong)size, out PrivateRange range)) @@ -491,44 +448,11 @@ namespace Ryujinx.Cpu.Jit return regions; } - private void ReadImpl(ulong va, Span data) - { - if (data.Length == 0) - { - return; - } - - try - { - AssertValidAddressAndSize(va, (ulong)data.Length); - - ulong endVa = va + (ulong)data.Length; - int offset = 0; - - while (va < endVa) - { - (MemoryBlock memory, ulong rangeOffset, ulong copySize) = GetMemoryOffsetAndSize(va, (ulong)(data.Length - offset)); - - memory.GetSpan(rangeOffset, (int)copySize).CopyTo(data.Slice(offset, (int)copySize)); - - va += copySize; - offset += (int)copySize; - } - } - catch (InvalidMemoryRegionException) - { - if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) - { - throw; - } - } - } - /// /// /// This function also validates that the given range is both valid and mapped, and will throw if it is not. /// - public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) + public override void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) { AssertValidAddressAndSize(va, size); @@ -543,23 +467,6 @@ namespace Ryujinx.Cpu.Jit _pages.SignalMemoryTracking(Tracking, va, size, write, exemptId); } - /// - /// Computes the number of pages in a virtual address range. - /// - /// Virtual address of the range - /// Size of the range - /// The virtual address of the beginning of the first page - /// This function does not differentiate between allocated and unallocated pages. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int GetPagesCount(ulong va, ulong size, out ulong startVa) - { - // WARNING: Always check if ulong does not overflow during the operations. - startVa = va & ~(ulong)PageMask; - ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask; - - return (int)(vaSpan / PageSize); - } - public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None) { return Tracking.BeginTracking(address, size, id, flags); @@ -618,10 +525,44 @@ namespace Ryujinx.Cpu.Jit _nativePageTable.Dispose(); } - protected override Span GetPhysicalAddressSpan(ulong pa, int size) + protected override Memory GetPhysicalAddressMemory(nuint pa, int size) + => _backingMemory.GetMemory(pa, size); + + protected override Span GetPhysicalAddressSpan(nuint pa, int size) => _backingMemory.GetSpan(pa, size); - protected override ulong TranslateVirtualAddressForRead(ulong va) - => GetPhysicalAddressInternal(va); + protected override void WriteImpl(ulong va, ReadOnlySpan data) + { + try + { + AssertValidAddressAndSize(va, (ulong)data.Length); + + ulong endVa = va + (ulong)data.Length; + int offset = 0; + + while (va < endVa) + { + (MemoryBlock memory, ulong rangeOffset, ulong copySize) = GetMemoryOffsetAndSize(va, (ulong)(data.Length - offset)); + + data.Slice(offset, (int)copySize).CopyTo(memory.GetSpan(rangeOffset, (int)copySize)); + + va += copySize; + offset += (int)copySize; + } + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + } + } + + protected override nuint TranslateVirtualAddressChecked(ulong va) + => (nuint)GetPhysicalAddressChecked(va); + + protected override nuint TranslateVirtualAddressUnchecked(ulong va) + => (nuint)GetPhysicalAddressInternal(va); } } diff --git a/src/Ryujinx.Cpu/VirtualMemoryManagerRefCountedBase.cs b/src/Ryujinx.Cpu/VirtualMemoryManagerRefCountedBase.cs index c2d8cfb1a0..3c7b338055 100644 --- a/src/Ryujinx.Cpu/VirtualMemoryManagerRefCountedBase.cs +++ b/src/Ryujinx.Cpu/VirtualMemoryManagerRefCountedBase.cs @@ -1,13 +1,10 @@ using Ryujinx.Memory; using System.Diagnostics; -using System.Numerics; using System.Threading; namespace Ryujinx.Cpu { - public abstract class VirtualMemoryManagerRefCountedBase : VirtualMemoryManagerBase, IRefCounted - where TVirtual : IBinaryInteger - where TPhysical : IBinaryInteger + public abstract class VirtualMemoryManagerRefCountedBase : VirtualMemoryManagerBase, IRefCounted { private int _referenceCount; diff --git a/src/Ryujinx.Memory/AddressSpaceManager.cs b/src/Ryujinx.Memory/AddressSpaceManager.cs index f19b45b659..f089c85736 100644 --- a/src/Ryujinx.Memory/AddressSpaceManager.cs +++ b/src/Ryujinx.Memory/AddressSpaceManager.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; namespace Ryujinx.Memory { @@ -11,7 +10,7 @@ namespace Ryujinx.Memory /// Represents a address space manager. /// Supports virtual memory region mapping, address translation and read/write access to mapped regions. ///
- public sealed class AddressSpaceManager : VirtualMemoryManagerBase, IVirtualMemoryManager, IWritableBlock + public sealed class AddressSpaceManager : VirtualMemoryManagerBase, IVirtualMemoryManager { /// public bool Supports4KBPages => true; @@ -63,8 +62,7 @@ namespace Ryujinx.Memory } } - /// - public void MapForeign(ulong va, nuint hostPointer, ulong size) + public override void MapForeign(ulong va, nuint hostPointer, ulong size) { AssertValidAddressAndSize(va, size); @@ -92,106 +90,6 @@ namespace Ryujinx.Memory } } - /// - public T Read(ulong va) where T : unmanaged - { - return MemoryMarshal.Cast(GetSpan(va, Unsafe.SizeOf()))[0]; - } - - /// - public void Write(ulong va, T value) where T : unmanaged - { - Write(va, MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1))); - } - - /// - public void Write(ulong va, ReadOnlySpan data) - { - if (data.Length == 0) - { - return; - } - - AssertValidAddressAndSize(va, (ulong)data.Length); - - if (IsContiguousAndMapped(va, data.Length)) - { - data.CopyTo(GetHostSpanContiguous(va, data.Length)); - } - else - { - int offset = 0, size; - - if ((va & PageMask) != 0) - { - size = Math.Min(data.Length, PageSize - (int)(va & PageMask)); - - data[..size].CopyTo(GetHostSpanContiguous(va, size)); - - offset += size; - } - - for (; offset < data.Length; offset += size) - { - size = Math.Min(data.Length - offset, PageSize); - - data.Slice(offset, size).CopyTo(GetHostSpanContiguous(va + (ulong)offset, size)); - } - } - } - - /// - public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data) - { - Write(va, data); - - return true; - } - - /// - public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) - { - if (size == 0) - { - return ReadOnlySpan.Empty; - } - - if (IsContiguousAndMapped(va, size)) - { - return GetHostSpanContiguous(va, size); - } - else - { - Span data = new byte[size]; - - Read(va, data); - - return data; - } - } - - /// - public unsafe WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) - { - if (size == 0) - { - return new WritableRegion(null, va, Memory.Empty); - } - - if (IsContiguousAndMapped(va, size)) - { - return new WritableRegion(null, va, new NativeMemoryManager((byte*)GetHostAddress(va), size).Memory); - } - else - { - Memory memory = new byte[size]; - - GetSpan(va, size).CopyTo(memory.Span); - - return new WritableRegion(this, va, memory); - } - } - /// public unsafe ref T GetRef(ulong va) where T : unmanaged { @@ -203,50 +101,6 @@ namespace Ryujinx.Memory return ref *(T*)GetHostAddress(va); } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetPagesCount(ulong va, uint size, out ulong startVa) - { - // WARNING: Always check if ulong does not overflow during the operations. - startVa = va & ~(ulong)PageMask; - ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask; - - return (int)(vaSpan / PageSize); - } - - private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool IsContiguousAndMapped(ulong va, int size) => IsContiguous(va, size) && IsMapped(va); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool IsContiguous(ulong va, int size) - { - if (!ValidateAddress(va) || !ValidateAddressAndSize(va, (ulong)size)) - { - return false; - } - - int pages = GetPagesCount(va, (uint)size, out va); - - for (int page = 0; page < pages - 1; page++) - { - if (!ValidateAddress(va + PageSize)) - { - return false; - } - - if (GetHostAddress(va) + PageSize != GetHostAddress(va + PageSize)) - { - return false; - } - - va += PageSize; - } - - return true; - } - /// public IEnumerable GetHostRegions(ulong va, ulong size) { @@ -304,7 +158,7 @@ namespace Ryujinx.Memory return null; } - int pages = GetPagesCount(va, (uint)size, out va); + int pages = GetPagesCount(va, size, out va); var regions = new List(); @@ -336,9 +190,8 @@ namespace Ryujinx.Memory return regions; } - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsMapped(ulong va) + public override bool IsMapped(ulong va) { if (!ValidateAddress(va)) { @@ -351,7 +204,7 @@ namespace Ryujinx.Memory /// public bool IsRangeMapped(ulong va, ulong size) { - if (size == 0UL) + if (size == 0) { return true; } @@ -376,11 +229,6 @@ namespace Ryujinx.Memory return true; } - private unsafe Span GetHostSpanContiguous(ulong va, int size) - { - return new Span((void*)GetHostAddress(va), size); - } - private nuint GetHostAddress(ulong va) { return _pageTable.Read(va) + (nuint)(va & PageMask); @@ -397,16 +245,16 @@ namespace Ryujinx.Memory throw new NotImplementedException(); } - /// - public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) - { - // Only the ARM Memory Manager has tracking for now. - } + protected unsafe override Memory GetPhysicalAddressMemory(nuint pa, int size) + => new NativeMemoryManager((byte*)pa, size).Memory; protected override unsafe Span GetPhysicalAddressSpan(nuint pa, int size) - => new((void*)pa, size); + => new Span((void*)pa, size); - protected override nuint TranslateVirtualAddressForRead(ulong va) + protected override nuint TranslateVirtualAddressChecked(ulong va) + => GetHostAddress(va); + + protected override nuint TranslateVirtualAddressUnchecked(ulong va) => GetHostAddress(va); } } diff --git a/src/Ryujinx.Memory/BytesReadOnlySequenceSegment.cs b/src/Ryujinx.Memory/BytesReadOnlySequenceSegment.cs new file mode 100644 index 0000000000..5fe8d936c3 --- /dev/null +++ b/src/Ryujinx.Memory/BytesReadOnlySequenceSegment.cs @@ -0,0 +1,60 @@ +using System; +using System.Buffers; +using System.Runtime.InteropServices; + +namespace Ryujinx.Memory +{ + /// + /// A concrete implementation of , + /// with methods to help build a full sequence. + /// + public sealed class BytesReadOnlySequenceSegment : ReadOnlySequenceSegment + { + public BytesReadOnlySequenceSegment(Memory memory) => Memory = memory; + + public BytesReadOnlySequenceSegment Append(Memory memory) + { + var nextSegment = new BytesReadOnlySequenceSegment(memory) + { + RunningIndex = RunningIndex + Memory.Length + }; + + Next = nextSegment; + + return nextSegment; + } + + /// + /// Attempts to determine if the current and are contiguous. + /// Only works if both were created by a . + /// + /// The segment to check if continuous with the current one + /// The starting address of the contiguous segment + /// The size of the contiguous segment + /// True if the segments are contiguous, otherwise false + public unsafe bool IsContiguousWith(Memory other, out nuint contiguousStart, out int contiguousSize) + { + if (MemoryMarshal.TryGetMemoryManager>(Memory, out var thisMemoryManager) && + MemoryMarshal.TryGetMemoryManager>(other, out var otherMemoryManager) && + thisMemoryManager.Pointer + thisMemoryManager.Length == otherMemoryManager.Pointer) + { + contiguousStart = (nuint)thisMemoryManager.Pointer; + contiguousSize = thisMemoryManager.Length + otherMemoryManager.Length; + return true; + } + else + { + contiguousStart = 0; + contiguousSize = 0; + return false; + } + } + + /// + /// Replaces the current value with the one provided. + /// + /// The new segment to hold in this + public void Replace(Memory memory) + => Memory = memory; + } +} diff --git a/src/Ryujinx.Memory/IVirtualMemoryManager.cs b/src/Ryujinx.Memory/IVirtualMemoryManager.cs index 557da2f261..96d3e85797 100644 --- a/src/Ryujinx.Memory/IVirtualMemoryManager.cs +++ b/src/Ryujinx.Memory/IVirtualMemoryManager.cs @@ -124,6 +124,16 @@ namespace Ryujinx.Memory } } + /// + /// Gets a read-only sequence of read-only memory blocks from CPU mapped memory. + /// + /// Virtual address of the data + /// Size of the data + /// True if read tracking is triggered on the memory + /// A read-only sequence of read-only memory of the data + /// Throw for unhandled invalid or unmapped memory accesses + ReadOnlySequence GetReadOnlySequence(ulong va, int size, bool tracked = false); + /// /// Gets a read-only span of data from CPU mapped memory. /// diff --git a/src/Ryujinx.Memory/NativeMemoryManager.cs b/src/Ryujinx.Memory/NativeMemoryManager.cs index fe718bda81..9ca6329382 100644 --- a/src/Ryujinx.Memory/NativeMemoryManager.cs +++ b/src/Ryujinx.Memory/NativeMemoryManager.cs @@ -14,6 +14,10 @@ namespace Ryujinx.Memory _length = length; } + public unsafe T* Pointer => _pointer; + + public int Length => _length; + public override Span GetSpan() { return new Span((void*)_pointer, _length); diff --git a/src/Ryujinx.Memory/VirtualMemoryManagerBase.cs b/src/Ryujinx.Memory/VirtualMemoryManagerBase.cs index cbec88cc56..506e25f668 100644 --- a/src/Ryujinx.Memory/VirtualMemoryManagerBase.cs +++ b/src/Ryujinx.Memory/VirtualMemoryManagerBase.cs @@ -1,34 +1,171 @@ +using Ryujinx.Common.Memory; using System; -using System.Numerics; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace Ryujinx.Memory { - public abstract class VirtualMemoryManagerBase - where TVirtual : IBinaryInteger - where TPhysical : IBinaryInteger + public abstract class VirtualMemoryManagerBase : IWritableBlock { public const int PageBits = 12; public const int PageSize = 1 << PageBits; public const int PageMask = PageSize - 1; - protected abstract TVirtual AddressSpaceSize { get; } + protected abstract ulong AddressSpaceSize { get; } - public virtual void Read(TVirtual va, Span data) + public virtual ReadOnlySequence GetReadOnlySequence(ulong va, int size, bool tracked = false) + { + if (size == 0) + { + return ReadOnlySequence.Empty; + } + + if (tracked) + { + SignalMemoryTracking(va, (ulong)size, false); + } + + if (IsContiguousAndMapped(va, size)) + { + nuint pa = TranslateVirtualAddressUnchecked(va); + + return new ReadOnlySequence(GetPhysicalAddressMemory(pa, size)); + } + else + { + AssertValidAddressAndSize(va, size); + + int offset = 0, segmentSize; + + BytesReadOnlySequenceSegment first = null, last = null; + + if ((va & PageMask) != 0) + { + nuint pa = TranslateVirtualAddressChecked(va); + + segmentSize = Math.Min(size, PageSize - (int)(va & PageMask)); + + Memory memory = GetPhysicalAddressMemory(pa, segmentSize); + + first = last = new BytesReadOnlySequenceSegment(memory); + + offset += segmentSize; + } + + for (; offset < size; offset += segmentSize) + { + nuint pa = TranslateVirtualAddressChecked(va + (ulong)offset); + + segmentSize = Math.Min(size - offset, PageSize); + + Memory memory = GetPhysicalAddressMemory(pa, segmentSize); + + if (first is null) + { + first = last = new BytesReadOnlySequenceSegment(memory); + } + else + { + if (last.IsContiguousWith(memory, out nuint contiguousStart, out int contiguousSize)) + { + last.Replace(GetPhysicalAddressMemory(contiguousStart, contiguousSize)); + } + else + { + last = last.Append(memory); + } + } + } + + return new ReadOnlySequence(first, 0, last, (int)(size - last.RunningIndex)); + } + } + + public virtual ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) + { + if (size == 0) + { + return ReadOnlySpan.Empty; + } + + if (tracked) + { + SignalMemoryTracking(va, (ulong)size, false); + } + + if (IsContiguousAndMapped(va, size)) + { + nuint pa = TranslateVirtualAddressUnchecked(va); + + return GetPhysicalAddressSpan(pa, size); + } + else + { + Span data = new byte[size]; + + Read(va, data); + + return data; + } + } + + public virtual WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) + { + if (size == 0) + { + return new WritableRegion(null, va, Memory.Empty); + } + + if (tracked) + { + SignalMemoryTracking(va, (ulong)size, true); + } + + if (IsContiguousAndMapped(va, size)) + { + nuint pa = TranslateVirtualAddressUnchecked(va); + + return new WritableRegion(null, va, GetPhysicalAddressMemory(pa, size)); + } + else + { + IMemoryOwner memoryOwner = ByteMemoryPool.Rent(size); + + Read(va, memoryOwner.Memory.Span); + + return new WritableRegion(this, va, memoryOwner); + } + } + + public abstract bool IsMapped(ulong va); + + public virtual void MapForeign(ulong va, nuint hostPointer, ulong size) + { + throw new NotSupportedException(); + } + + public virtual T Read(ulong va) where T : unmanaged + { + return MemoryMarshal.Cast(GetSpan(va, Unsafe.SizeOf()))[0]; + } + + public virtual void Read(ulong va, Span data) { if (data.Length == 0) { return; } - AssertValidAddressAndSize(va, TVirtual.CreateChecked(data.Length)); + AssertValidAddressAndSize(va, data.Length); int offset = 0, size; - if ((int.CreateTruncating(va) & PageMask) != 0) + if ((va & PageMask) != 0) { - TPhysical pa = TranslateVirtualAddressForRead(va); + nuint pa = TranslateVirtualAddressChecked(va); - size = Math.Min(data.Length, PageSize - ((int.CreateTruncating(va) & PageMask))); + size = Math.Min(data.Length, PageSize - (int)(va & PageMask)); GetPhysicalAddressSpan(pa, size).CopyTo(data[..size]); @@ -37,7 +174,7 @@ namespace Ryujinx.Memory for (; offset < data.Length; offset += size) { - TPhysical pa = TranslateVirtualAddressForRead(va + TVirtual.CreateChecked(offset)); + nuint pa = TranslateVirtualAddressChecked(va + (ulong)offset); size = Math.Min(data.Length - offset, PageSize); @@ -45,13 +182,84 @@ namespace Ryujinx.Memory } } + public virtual T ReadTracked(ulong va) where T : unmanaged + { + SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), false); + + return Read(va); + } + + public virtual void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) + { + // No default implementation + } + + public virtual void Write(ulong va, ReadOnlySpan data) + { + if (data.Length == 0) + { + return; + } + + SignalMemoryTracking(va, (ulong)data.Length, true); + + WriteImpl(va, data); + } + + public virtual void Write(ulong va, T value) where T : unmanaged + { + Write(va, MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1))); + } + + public virtual void WriteUntracked(ulong va, ReadOnlySpan data) + { + if (data.Length == 0) + { + return; + } + + WriteImpl(va, data); + } + + public virtual bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data) + { + if (data.Length == 0) + { + return false; + } + + if (IsContiguousAndMapped(va, data.Length)) + { + SignalMemoryTracking(va, (ulong)data.Length, false); + + nuint pa = TranslateVirtualAddressChecked(va); + + var target = GetPhysicalAddressSpan(pa, data.Length); + + bool changed = !data.SequenceEqual(target); + + if (changed) + { + data.CopyTo(target); + } + + return changed; + } + else + { + Write(va, data); + + return true; + } + } + /// /// Ensures the combination of virtual address and size is part of the addressable space. /// /// Virtual address of the range /// Size of the range in bytes /// Throw when the memory region specified outside the addressable space - protected void AssertValidAddressAndSize(TVirtual va, TVirtual size) + protected void AssertValidAddressAndSize(ulong va, ulong size) { if (!ValidateAddressAndSize(va, size)) { @@ -59,16 +267,82 @@ namespace Ryujinx.Memory } } - protected abstract Span GetPhysicalAddressSpan(TPhysical pa, int size); + /// + /// Ensures the combination of virtual address and size is part of the addressable space. + /// + /// Virtual address of the range + /// Size of the range in bytes + /// Throw when the memory region specified outside the addressable space + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void AssertValidAddressAndSize(ulong va, int size) + => AssertValidAddressAndSize(va, (ulong)size); - protected abstract TPhysical TranslateVirtualAddressForRead(TVirtual 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)] + protected static int GetPagesCount(ulong va, ulong size, out ulong startVa) + { + // WARNING: Always check if ulong does not overflow during the operations. + startVa = va & ~(ulong)PageMask; + ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask; + + return (int)(vaSpan / PageSize); + } + + protected abstract Memory GetPhysicalAddressMemory(nuint pa, int size); + + protected abstract Span GetPhysicalAddressSpan(nuint pa, int size); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected bool IsContiguous(ulong va, int size) => IsContiguous(va, (ulong)size); + + protected virtual bool IsContiguous(ulong va, ulong size) + { + if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size)) + { + return false; + } + + int pages = GetPagesCount(va, size, out va); + + for (int page = 0; page < pages - 1; page++) + { + if (!ValidateAddress(va + PageSize)) + { + return false; + } + + if (TranslateVirtualAddressUnchecked(va) + PageSize != TranslateVirtualAddressUnchecked(va + PageSize)) + { + return false; + } + + va += PageSize; + } + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected bool IsContiguousAndMapped(ulong va, int size) + => IsContiguous(va, size) && IsMapped(va); + + protected abstract nuint TranslateVirtualAddressChecked(ulong va); + + protected abstract nuint TranslateVirtualAddressUnchecked(ulong va); /// /// Checks if the virtual address is part of the addressable space. /// /// Virtual address /// True if the virtual address is part of the addressable space - protected bool ValidateAddress(TVirtual va) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected bool ValidateAddress(ulong va) { return va < AddressSpaceSize; } @@ -79,13 +353,53 @@ namespace Ryujinx.Memory /// Virtual address of the range /// Size of the range in bytes /// True if the combination of virtual address and size is part of the addressable space - protected bool ValidateAddressAndSize(TVirtual va, TVirtual size) + protected bool ValidateAddressAndSize(ulong va, ulong size) { - TVirtual endVa = va + size; + ulong endVa = va + size; return endVa >= va && endVa >= size && endVa <= AddressSpaceSize; } protected static void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message); + + protected static void ThrowMemoryNotContiguous() + => throw new MemoryNotContiguousException(); + + protected virtual void WriteImpl(ulong va, ReadOnlySpan data) + { + AssertValidAddressAndSize(va, data.Length); + + if (IsContiguousAndMapped(va, data.Length)) + { + nuint pa = TranslateVirtualAddressUnchecked(va); + + data.CopyTo(GetPhysicalAddressSpan(pa, data.Length)); + } + else + { + int offset = 0, size; + + if ((va & PageMask) != 0) + { + nuint pa = TranslateVirtualAddressChecked(va); + + size = Math.Min(data.Length, PageSize - (int)(va & PageMask)); + + data[..size].CopyTo(GetPhysicalAddressSpan(pa, size)); + + offset += size; + } + + for (; offset < data.Length; offset += size) + { + nuint pa = TranslateVirtualAddressChecked(va + (ulong)offset); + + size = Math.Min(data.Length - offset, PageSize); + + data.Slice(offset, size).CopyTo(GetPhysicalAddressSpan(pa, size)); + } + } + } + } } diff --git a/src/Ryujinx.Memory/WritableRegion.cs b/src/Ryujinx.Memory/WritableRegion.cs index 666c8a99b2..2c21ef4e80 100644 --- a/src/Ryujinx.Memory/WritableRegion.cs +++ b/src/Ryujinx.Memory/WritableRegion.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; namespace Ryujinx.Memory { @@ -6,6 +7,7 @@ namespace Ryujinx.Memory { private readonly IWritableBlock _block; private readonly ulong _va; + private readonly IMemoryOwner _memoryOwner; private readonly bool _tracked; private bool NeedsWriteback => _block != null; @@ -20,6 +22,12 @@ namespace Ryujinx.Memory Memory = memory; } + public WritableRegion(IWritableBlock block, ulong va, IMemoryOwner memoryOwner, bool tracked = false) + : this(block, va, memoryOwner.Memory, tracked) + { + _memoryOwner = memoryOwner; + } + public void Dispose() { if (NeedsWriteback) @@ -33,6 +41,8 @@ namespace Ryujinx.Memory _block.WriteUntracked(_va, Memory.Span); } } + + _memoryOwner?.Dispose(); } } } diff --git a/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs b/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs index 85a1ac02bc..15e7d9b89a 100644 --- a/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs +++ b/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs @@ -1,6 +1,7 @@ using Ryujinx.Memory; using Ryujinx.Memory.Range; using System; +using System.Buffers; using System.Collections.Generic; namespace Ryujinx.Tests.Memory @@ -57,6 +58,11 @@ namespace Ryujinx.Tests.Memory throw new NotImplementedException(); } + public ReadOnlySequence GetReadOnlySequence(ulong va, int size, bool tracked = false) + { + throw new NotImplementedException(); + } + public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) { throw new NotImplementedException(); From 8c2da1aa04de7526fa670ed6099a6e713e1a234f Mon Sep 17 00:00:00 2001 From: gdkchan Date: Fri, 5 Apr 2024 12:54:08 -0300 Subject: [PATCH 24/87] Add missing ModWindowTitle locale key (#6601) * Add missing ModWindowTitle locale key * Fix this while I'm at it --- src/Ryujinx/AppHost.cs | 50 +++++++++---------- src/Ryujinx/Assets/Locales/en_US.json | 1 + .../UI/Windows/ModManagerWindow.axaml.cs | 2 +- 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs index 868d194fb3..d69bfc147f 100644 --- a/src/Ryujinx/AppHost.cs +++ b/src/Ryujinx/AppHost.cs @@ -777,31 +777,31 @@ namespace Ryujinx.Ava var memoryConfiguration = ConfigurationState.Instance.System.ExpandRam.Value ? MemoryConfiguration.MemoryConfiguration6GiB : MemoryConfiguration.MemoryConfiguration4GiB; HLEConfiguration configuration = new(VirtualFileSystem, - _viewModel.LibHacHorizonManager, - ContentManager, - _accountManager, - _userChannelPersistence, - renderer, - InitializeAudio(), - memoryConfiguration, - _viewModel.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, - ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None, - 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, - ConfigurationState.Instance.System.UseHypervisor, - ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value, - ConfigurationState.Instance.Multiplayer.Mode); + _viewModel.LibHacHorizonManager, + ContentManager, + _accountManager, + _userChannelPersistence, + renderer, + InitializeAudio(), + memoryConfiguration, + _viewModel.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, + ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None, + 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, + ConfigurationState.Instance.System.UseHypervisor, + ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value, + ConfigurationState.Instance.Multiplayer.Mode); Device = new Switch(configuration); } diff --git a/src/Ryujinx/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json index 3a3cb3017d..ef40fd5b2e 100644 --- a/src/Ryujinx/Assets/Locales/en_US.json +++ b/src/Ryujinx/Assets/Locales/en_US.json @@ -597,6 +597,7 @@ "UserProfileWindowTitle": "User Profiles Manager", "CheatWindowTitle": "Cheats Manager", "DlcWindowTitle": "Manage Downloadable Content for {0} ({1})", + "ModWindowTitle": "Manage Mods for {0} ({1})", "UpdateWindowTitle": "Title Update Manager", "CheatWindowHeading": "Cheats Available for {0} [{1}]", "BuildId": "BuildId:", diff --git a/src/Ryujinx/UI/Windows/ModManagerWindow.axaml.cs b/src/Ryujinx/UI/Windows/ModManagerWindow.axaml.cs index d9ae0d4f32..98f694db81 100644 --- a/src/Ryujinx/UI/Windows/ModManagerWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/ModManagerWindow.axaml.cs @@ -38,7 +38,7 @@ namespace Ryujinx.Ava.UI.Windows SecondaryButtonText = "", CloseButtonText = "", Content = new ModManagerWindow(titleId), - Title = string.Format(LocaleManager.Instance[LocaleKeys.ModWindowHeading], titleName, titleId.ToString("X16")), + Title = string.Format(LocaleManager.Instance[LocaleKeys.ModWindowTitle], titleName, titleId.ToString("X16")), }; Style bottomBorder = new(x => x.OfType().Name("DialogSpace").Child().OfType()); From 05c041feeba085275259f3b434bac4535b7a46d7 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Fri, 5 Apr 2024 15:26:45 -0300 Subject: [PATCH 25/87] Ignore diacritics on game search (#6602) --- src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index 17bd69b14c..036a536e5c 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -36,6 +36,7 @@ using SixLabors.ImageSharp.PixelFormats; using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Globalization; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -980,7 +981,14 @@ namespace Ryujinx.Ava.UI.ViewModels { if (arg is ApplicationData app) { - return string.IsNullOrWhiteSpace(_searchText) || app.TitleName.ToLower().Contains(_searchText.ToLower()); + if (string.IsNullOrWhiteSpace(_searchText)) + { + return true; + } + + CompareInfo compareInfo = CultureInfo.CurrentCulture.CompareInfo; + + return compareInfo.IndexOf(app.TitleName, _searchText, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace) >= 0; } return false; From 6d28b643126ee5de476a89f3ff94b5cc3ead3970 Mon Sep 17 00:00:00 2001 From: Ac_K Date: Fri, 5 Apr 2024 20:45:43 +0200 Subject: [PATCH 26/87] ts: Migrate service to Horizon project (#6514) * ts: Migrate service to Horizon project This PR migrate the `ts` service (stored in `ptm`) to the Horizon project: - It stubs all known IPCs. - IpcServer consts are checked by RE. Closes #6480 * Fix args --- .../HOS/Services/Ptm/Ts/IMeasurementServer.cs | 39 ------------ .../Ptm/Ipc/MeasurementServer.cs | 63 +++++++++++++++++++ src/Ryujinx.Horizon/Ptm/Ipc/Session.cs | 47 ++++++++++++++ src/Ryujinx.Horizon/Ptm/TsIpcServer.cs | 44 +++++++++++++ src/Ryujinx.Horizon/Ptm/TsMain.cs | 17 +++++ src/Ryujinx.Horizon/Sdk/Ts/DeviceCode.cs | 8 +++ .../Sdk/Ts/IMeasurementServer.cs | 14 +++++ src/Ryujinx.Horizon/Sdk/Ts/ISession.cs | 12 ++++ .../Sdk/Ts}/Location.cs | 2 +- src/Ryujinx.Horizon/ServiceTable.cs | 2 + 10 files changed, 208 insertions(+), 40 deletions(-) delete mode 100644 src/Ryujinx.HLE/HOS/Services/Ptm/Ts/IMeasurementServer.cs create mode 100644 src/Ryujinx.Horizon/Ptm/Ipc/MeasurementServer.cs create mode 100644 src/Ryujinx.Horizon/Ptm/Ipc/Session.cs create mode 100644 src/Ryujinx.Horizon/Ptm/TsIpcServer.cs create mode 100644 src/Ryujinx.Horizon/Ptm/TsMain.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ts/DeviceCode.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ts/IMeasurementServer.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ts/ISession.cs rename src/{Ryujinx.HLE/HOS/Services/Ptm/Ts/Types => Ryujinx.Horizon/Sdk/Ts}/Location.cs (61%) diff --git a/src/Ryujinx.HLE/HOS/Services/Ptm/Ts/IMeasurementServer.cs b/src/Ryujinx.HLE/HOS/Services/Ptm/Ts/IMeasurementServer.cs deleted file mode 100644 index 66ffd0a49e..0000000000 --- a/src/Ryujinx.HLE/HOS/Services/Ptm/Ts/IMeasurementServer.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Ryujinx.Common.Logging; -using Ryujinx.HLE.HOS.Services.Ptm.Ts.Types; - -namespace Ryujinx.HLE.HOS.Services.Ptm.Ts -{ - [Service("ts")] - class IMeasurementServer : IpcService - { - private const uint DefaultTemperature = 42u; - - public IMeasurementServer(ServiceCtx context) { } - - [CommandCmif(1)] - // GetTemperature(Location location) -> u32 - public ResultCode GetTemperature(ServiceCtx context) - { - Location location = (Location)context.RequestData.ReadByte(); - - Logger.Stub?.PrintStub(LogClass.ServicePtm, new { location }); - - context.ResponseData.Write(DefaultTemperature); - - return ResultCode.Success; - } - - [CommandCmif(3)] - // GetTemperatureMilliC(Location location) -> u32 - public ResultCode GetTemperatureMilliC(ServiceCtx context) - { - Location location = (Location)context.RequestData.ReadByte(); - - Logger.Stub?.PrintStub(LogClass.ServicePtm, new { location }); - - context.ResponseData.Write(DefaultTemperature * 1000); - - return ResultCode.Success; - } - } -} diff --git a/src/Ryujinx.Horizon/Ptm/Ipc/MeasurementServer.cs b/src/Ryujinx.Horizon/Ptm/Ipc/MeasurementServer.cs new file mode 100644 index 0000000000..ce7c0474a7 --- /dev/null +++ b/src/Ryujinx.Horizon/Ptm/Ipc/MeasurementServer.cs @@ -0,0 +1,63 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Ts; +using Ryujinx.Horizon.Ts.Ipc; + +namespace Ryujinx.Horizon.Ptm.Ipc +{ + partial class MeasurementServer : IMeasurementServer + { + // NOTE: Values are randomly choosen. + public const int DefaultTemperature = 42; + public const int MinimumTemperature = 0; + public const int MaximumTemperature = 100; + + [CmifCommand(0)] // 1.0.0-16.1.0 + public Result GetTemperatureRange(out int minimumTemperature, out int maximumTemperature, Location location) + { + Logger.Stub?.PrintStub(LogClass.ServicePtm, new { location }); + + minimumTemperature = MinimumTemperature; + maximumTemperature = MaximumTemperature; + + return Result.Success; + } + + [CmifCommand(1)] // 1.0.0-16.1.0 + public Result GetTemperature(out int temperature, Location location) + { + Logger.Stub?.PrintStub(LogClass.ServicePtm, new { location }); + + temperature = DefaultTemperature; + + return Result.Success; + } + + [CmifCommand(2)] // 1.0.0-13.2.1 + public Result SetMeasurementMode(Location location, byte measurementMode) + { + Logger.Stub?.PrintStub(LogClass.ServicePtm, new { location, measurementMode }); + + return Result.Success; + } + + [CmifCommand(3)] // 1.0.0-13.2.1 + public Result GetTemperatureMilliC(out int temperatureMilliC, Location location) + { + Logger.Stub?.PrintStub(LogClass.ServicePtm, new { location }); + + temperatureMilliC = DefaultTemperature * 1000; + + return Result.Success; + } + + [CmifCommand(4)] // 8.0.0+ + public Result OpenSession(out ISession session, DeviceCode deviceCode) + { + session = new Session(deviceCode); + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Ptm/Ipc/Session.cs b/src/Ryujinx.Horizon/Ptm/Ipc/Session.cs new file mode 100644 index 0000000000..191a4b3af6 --- /dev/null +++ b/src/Ryujinx.Horizon/Ptm/Ipc/Session.cs @@ -0,0 +1,47 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Ptm.Ipc; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Ts; + +namespace Ryujinx.Horizon.Ts.Ipc +{ + partial class Session : ISession + { + private readonly DeviceCode _deviceCode; + + public Session(DeviceCode deviceCode) + { + _deviceCode = deviceCode; + } + + [CmifCommand(0)] + public Result GetTemperatureRange(out int minimumTemperature, out int maximumTemperature) + { + Logger.Stub?.PrintStub(LogClass.ServicePtm, new { _deviceCode }); + + minimumTemperature = MeasurementServer.MinimumTemperature; + maximumTemperature = MeasurementServer.MaximumTemperature; + + return Result.Success; + } + + [CmifCommand(2)] + public Result SetMeasurementMode(byte measurementMode) + { + Logger.Stub?.PrintStub(LogClass.ServicePtm, new { _deviceCode, measurementMode }); + + return Result.Success; + } + + [CmifCommand(4)] + public Result GetTemperature(out int temperature) + { + Logger.Stub?.PrintStub(LogClass.ServicePtm, new { _deviceCode }); + + temperature = MeasurementServer.DefaultTemperature; + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Ptm/TsIpcServer.cs b/src/Ryujinx.Horizon/Ptm/TsIpcServer.cs new file mode 100644 index 0000000000..db25d8e2e9 --- /dev/null +++ b/src/Ryujinx.Horizon/Ptm/TsIpcServer.cs @@ -0,0 +1,44 @@ +using Ryujinx.Horizon.Ptm.Ipc; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Ptm +{ + class TsIpcServer + { + private const int MaxSessionsCount = 4; + + private const int PointerBufferSize = 0; + private const int MaxDomains = 0; + private const int MaxDomainObjects = 0; + private const int MaxPortsCount = 1; + + private static readonly ManagerOptions _managerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ServerManager _serverManager; + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _managerOptions, MaxSessionsCount); + + _serverManager.RegisterObjectForServer(new MeasurementServer(), ServiceName.Encode("ts"), MaxSessionsCount); + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + _sm.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Ptm/TsMain.cs b/src/Ryujinx.Horizon/Ptm/TsMain.cs new file mode 100644 index 0000000000..237d52cd4c --- /dev/null +++ b/src/Ryujinx.Horizon/Ptm/TsMain.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Ptm +{ + class TsMain : IService + { + public static void Main(ServiceTable serviceTable) + { + TsIpcServer ipcServer = new(); + + ipcServer.Initialize(); + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ts/DeviceCode.cs b/src/Ryujinx.Horizon/Sdk/Ts/DeviceCode.cs new file mode 100644 index 0000000000..4fce4238ee --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ts/DeviceCode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Horizon.Sdk.Ts +{ + enum DeviceCode : uint + { + Internal = 0x41000001, + External = 0x41000002, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ts/IMeasurementServer.cs b/src/Ryujinx.Horizon/Sdk/Ts/IMeasurementServer.cs new file mode 100644 index 0000000000..ba9c2a748d --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ts/IMeasurementServer.cs @@ -0,0 +1,14 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Ts +{ + interface IMeasurementServer : IServiceObject + { + Result GetTemperatureRange(out int minimumTemperature, out int maximumTemperature, Location location); + Result GetTemperature(out int temperature, Location location); + Result SetMeasurementMode(Location location, byte measurementMode); + Result GetTemperatureMilliC(out int temperatureMilliC, Location location); + Result OpenSession(out ISession session, DeviceCode deviceCode); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ts/ISession.cs b/src/Ryujinx.Horizon/Sdk/Ts/ISession.cs new file mode 100644 index 0000000000..23c0d94f64 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ts/ISession.cs @@ -0,0 +1,12 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Ts +{ + interface ISession : IServiceObject + { + Result GetTemperatureRange(out int minimumTemperature, out int maximumTemperature); + Result GetTemperature(out int temperature); + Result SetMeasurementMode(byte measurementMode); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ptm/Ts/Types/Location.cs b/src/Ryujinx.Horizon/Sdk/Ts/Location.cs similarity index 61% rename from src/Ryujinx.HLE/HOS/Services/Ptm/Ts/Types/Location.cs rename to src/Ryujinx.Horizon/Sdk/Ts/Location.cs index 409188a979..177b0ee880 100644 --- a/src/Ryujinx.HLE/HOS/Services/Ptm/Ts/Types/Location.cs +++ b/src/Ryujinx.Horizon/Sdk/Ts/Location.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.HLE.HOS.Services.Ptm.Ts.Types +namespace Ryujinx.Horizon.Sdk.Ts { enum Location : byte { diff --git a/src/Ryujinx.Horizon/ServiceTable.cs b/src/Ryujinx.Horizon/ServiceTable.cs index b81e62a474..28c43a716f 100644 --- a/src/Ryujinx.Horizon/ServiceTable.cs +++ b/src/Ryujinx.Horizon/ServiceTable.cs @@ -11,6 +11,7 @@ using Ryujinx.Horizon.Ngc; using Ryujinx.Horizon.Ovln; using Ryujinx.Horizon.Prepo; using Ryujinx.Horizon.Psc; +using Ryujinx.Horizon.Ptm; using Ryujinx.Horizon.Sdk.Arp; using Ryujinx.Horizon.Srepo; using Ryujinx.Horizon.Usb; @@ -54,6 +55,7 @@ namespace Ryujinx.Horizon RegisterService(); RegisterService(); RegisterService(); + RegisterService(); RegisterService(); RegisterService(); From 22c0aa9c90a727830a4ef58a4f4f0faf8ca399e5 Mon Sep 17 00:00:00 2001 From: Marco Carvalho Date: Sat, 6 Apr 2024 08:36:18 -0300 Subject: [PATCH 27/87] "Task.Wait()" synchronously blocks, use "await" instead (#6598) --- src/Ryujinx/UI/Windows/MainWindow.axaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs index 33a9af5b61..37e2e71a8c 100644 --- a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs @@ -285,7 +285,7 @@ namespace Ryujinx.Ava.UI.Windows { _deferLoad = false; - ViewModel.LoadApplication(_launchPath, _startFullscreen).Wait(); + await ViewModel.LoadApplication(_launchPath, _startFullscreen); } } else From fb1171a21e515618047aee50c92c93390441edbb Mon Sep 17 00:00:00 2001 From: czcx <106482372+Mpro256@users.noreply.github.com> Date: Sat, 6 Apr 2024 07:45:24 -0400 Subject: [PATCH 28/87] Update README.md (#6575) --- docs/README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/README.md b/docs/README.md index 2213086f67..a22da3c7cf 100644 --- a/docs/README.md +++ b/docs/README.md @@ -33,8 +33,3 @@ Project Docs ================= To be added. Many project files will contain basic XML docs for key functions and classes in the meantime. - -Other Information -================= - -- N/A From c8bb05633e9da8ae5e98904108d1576c108dd436 Mon Sep 17 00:00:00 2001 From: WilliamWsyHK Date: Sat, 6 Apr 2024 19:47:01 +0800 Subject: [PATCH 29/87] Add mod enablement status in the log message (#6571) --- src/Ryujinx.HLE/HOS/ModLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ryujinx.HLE/HOS/ModLoader.cs b/src/Ryujinx.HLE/HOS/ModLoader.cs index 8ae529655c..de9d2a5039 100644 --- a/src/Ryujinx.HLE/HOS/ModLoader.cs +++ b/src/Ryujinx.HLE/HOS/ModLoader.cs @@ -218,7 +218,7 @@ namespace Ryujinx.HLE.HOS if (types.Length > 0) { - Logger.Info?.Print(LogClass.ModLoader, $"Found mod '{mod.Name}' [{types}]"); + Logger.Info?.Print(LogClass.ModLoader, $"Found {(mod.Enabled ? "enabled" : "disabled")} mod '{mod.Name}' [{types}]"); } } } From 66b1d59c666b425a8451e1a4fe981e3311be08fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 6 Apr 2024 14:07:06 +0200 Subject: [PATCH 30/87] nuget: bump DynamicData from 8.3.27 to 8.4.1 (#6536) Bumps [DynamicData](https://github.com/reactiveui/DynamicData) from 8.3.27 to 8.4.1. - [Release notes](https://github.com/reactiveui/DynamicData/releases) - [Changelog](https://github.com/reactivemarbles/DynamicData/blob/main/ReleaseNotes.md) - [Commits](https://github.com/reactiveui/DynamicData/compare/8.3.27...8.4.1) --- updated-dependencies: - dependency-name: DynamicData dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 455735fc46..5d6adcb367 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -13,7 +13,7 @@ - + From 791bf22109b90eca79fe1bf934074809661a6c86 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Sat, 6 Apr 2024 13:25:51 -0300 Subject: [PATCH 31/87] Vulkan: Skip draws when patches topology is used without a tessellation shader (#6508) --- src/Ryujinx.Graphics.Vulkan/PipelineBase.cs | 1 + src/Ryujinx.Graphics.Vulkan/PipelineState.cs | 11 +++++++++++ src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs | 2 ++ 3 files changed, 14 insertions(+) diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs index 2bcab51436..d5169a6884 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs @@ -981,6 +981,7 @@ namespace Ryujinx.Graphics.Vulkan _bindingBarriersDirty = true; _newState.PipelineLayout = internalProgram.PipelineLayout; + _newState.HasTessellationControlShader = internalProgram.HasTessellationControlShader; _newState.StagesCount = (uint)stages.Length; stages.CopyTo(_newState.Stages.AsSpan()[..stages.Length]); diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineState.cs b/src/Ryujinx.Graphics.Vulkan/PipelineState.cs index 25fd7168fb..49c12b3768 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineState.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineState.cs @@ -311,6 +311,7 @@ namespace Ryujinx.Graphics.Vulkan set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFBF) | ((value ? 1UL : 0UL) << 6); } + public bool HasTessellationControlShader; public NativeArray Stages; public PipelineLayout PipelineLayout; public SpecData SpecializationData; @@ -319,6 +320,7 @@ namespace Ryujinx.Graphics.Vulkan public void Initialize() { + HasTessellationControlShader = false; Stages = new NativeArray(Constants.MaxShaderStages); AdvancedBlendSrcPreMultiplied = true; @@ -419,6 +421,15 @@ namespace Ryujinx.Graphics.Vulkan PVertexBindingDescriptions = pVertexBindingDescriptions, }; + // Using patches topology without a tessellation shader is invalid. + // If we find such a case, return null pipeline to skip the draw. + if (Topology == PrimitiveTopology.PatchList && !HasTessellationControlShader) + { + program.AddGraphicsPipeline(ref Internal, null); + + return null; + } + bool primitiveRestartEnable = PrimitiveRestartEnable; bool topologySupportsRestart; diff --git a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs index e4ea0e4e61..b2be541bf7 100644 --- a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs +++ b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs @@ -21,6 +21,7 @@ namespace Ryujinx.Graphics.Vulkan public bool HasMinimalLayout { get; } public bool UsePushDescriptors { get; } public bool IsCompute { get; } + public bool HasTessellationControlShader => (Stages & (1u << 3)) != 0; public uint Stages { get; } @@ -461,6 +462,7 @@ namespace Ryujinx.Graphics.Vulkan stages[i] = _shaders[i].GetInfo(); } + pipeline.HasTessellationControlShader = HasTessellationControlShader; pipeline.StagesCount = (uint)_shaders.Length; pipeline.PipelineLayout = PipelineLayout; From 3be616207df437695208ceaec9b255db18768610 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Sat, 6 Apr 2024 13:38:52 -0300 Subject: [PATCH 32/87] Vulkan: Fix swapchain image view leak (#6509) --- .../RenderPassHolder.cs | 7 +++- src/Ryujinx.Graphics.Vulkan/TextureView.cs | 37 ++++++++++--------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs b/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs index 3d883b2d51..9edea5788d 100644 --- a/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs +++ b/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs @@ -122,7 +122,6 @@ namespace Ryujinx.Graphics.Vulkan gd.Api.CreateRenderPass(device, renderPassCreateInfo, null, out var renderPass).ThrowOnError(); - _renderPass?.Dispose(); _renderPass = new Auto(new DisposableRenderPass(gd.Api, device, renderPass)); } @@ -162,7 +161,7 @@ namespace Ryujinx.Graphics.Vulkan public void Dispose() { - // Dispose all framebuffers + // Dispose all framebuffers. foreach (var fb in _framebuffers.Values) { @@ -175,6 +174,10 @@ namespace Ryujinx.Graphics.Vulkan { texture.RemoveRenderPass(_key); } + + // Dispose render pass. + + _renderPass.Dispose(); } } } diff --git a/src/Ryujinx.Graphics.Vulkan/TextureView.cs b/src/Ryujinx.Graphics.Vulkan/TextureView.cs index 31d1396526..d918f965fa 100644 --- a/src/Ryujinx.Graphics.Vulkan/TextureView.cs +++ b/src/Ryujinx.Graphics.Vulkan/TextureView.cs @@ -4,6 +4,7 @@ using Silk.NET.Vulkan; using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using Format = Ryujinx.Graphics.GAL.Format; using VkBuffer = Silk.NET.Vulkan.Buffer; using VkFormat = Silk.NET.Vulkan.Format; @@ -36,7 +37,8 @@ namespace Ryujinx.Graphics.Vulkan public int FirstLayer { get; } public int FirstLevel { get; } public VkFormat VkFormat { get; } - public bool Valid { get; private set; } + private int _isValid; + public bool Valid => Volatile.Read(ref _isValid) != 0; public TextureView( VulkanRenderer gd, @@ -158,7 +160,7 @@ namespace Ryujinx.Graphics.Vulkan } } - Valid = true; + _isValid = 1; } /// @@ -178,7 +180,7 @@ namespace Ryujinx.Graphics.Vulkan VkFormat = format; - Valid = true; + _isValid = 1; } public Auto GetImage() @@ -1017,10 +1019,11 @@ namespace Ryujinx.Graphics.Vulkan { if (disposing) { - Valid = false; - - if (_gd.Textures.Remove(this)) + bool wasValid = Interlocked.Exchange(ref _isValid, 0) != 0; + if (wasValid) { + _gd.Textures.Remove(this); + _imageView.Dispose(); _imageView2dArray?.Dispose(); @@ -1034,7 +1037,7 @@ namespace Ryujinx.Graphics.Vulkan _imageViewDraw.Dispose(); } - Storage.DecrementViewsCount(); + Storage?.DecrementViewsCount(); if (_renderPasses != null) { @@ -1045,22 +1048,22 @@ namespace Ryujinx.Graphics.Vulkan pass.Dispose(); } } + + if (_selfManagedViews != null) + { + foreach (var view in _selfManagedViews.Values) + { + view.Dispose(); + } + + _selfManagedViews = null; + } } } } public void Dispose() { - if (_selfManagedViews != null) - { - foreach (var view in _selfManagedViews.Values) - { - view.Dispose(); - } - - _selfManagedViews = null; - } - Dispose(true); } From 12b235700cf104163bf8030df0feb6357a40f9d3 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Sat, 6 Apr 2024 13:51:44 -0300 Subject: [PATCH 33/87] Delete old 16KB page workarounds (#6584) * Delete old 16KB page workarounds * Rename Supports4KBPage to UsesPrivateAllocations * Format whitespace * This one should be false too * Update XML doc --- src/Ryujinx.Cpu/AddressSpace.cs | 437 +----------------- src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs | 2 +- src/Ryujinx.Cpu/Jit/MemoryManager.cs | 2 +- .../Jit/MemoryManagerHostMapped.cs | 2 +- .../Jit/MemoryManagerHostTracked.cs | 2 +- .../HOS/ArmProcessContextFactory.cs | 2 +- .../HOS/Kernel/Memory/KPageTable.cs | 2 +- .../HOS/Kernel/Memory/KPageTableBase.cs | 12 +- .../HOS/Kernel/Memory/KSharedMemory.cs | 13 +- src/Ryujinx.Memory/AddressSpaceManager.cs | 2 +- src/Ryujinx.Memory/IVirtualMemoryManager.cs | 6 +- .../MockVirtualMemoryManager.cs | 2 +- 12 files changed, 25 insertions(+), 459 deletions(-) diff --git a/src/Ryujinx.Cpu/AddressSpace.cs b/src/Ryujinx.Cpu/AddressSpace.cs index beea14beec..6664ed1345 100644 --- a/src/Ryujinx.Cpu/AddressSpace.cs +++ b/src/Ryujinx.Cpu/AddressSpace.cs @@ -1,5 +1,3 @@ -using Ryujinx.Common; -using Ryujinx.Common.Collections; using Ryujinx.Memory; using System; @@ -7,175 +5,23 @@ namespace Ryujinx.Cpu { public class AddressSpace : IDisposable { - private const int DefaultBlockAlignment = 1 << 20; - - private enum MappingType : byte - { - None, - Private, - Shared, - } - - private class Mapping : IntrusiveRedBlackTreeNode, IComparable - { - public ulong Address { get; private set; } - public ulong Size { get; private set; } - public ulong EndAddress => Address + Size; - public MappingType Type { get; private set; } - - public Mapping(ulong address, ulong size, MappingType type) - { - Address = address; - Size = size; - Type = type; - } - - public Mapping Split(ulong splitAddress) - { - ulong leftSize = splitAddress - Address; - ulong rightSize = EndAddress - splitAddress; - - Mapping left = new(Address, leftSize, Type); - - Address = splitAddress; - Size = rightSize; - - return left; - } - - public void UpdateState(MappingType newType) - { - Type = newType; - } - - public void Extend(ulong sizeDelta) - { - Size += sizeDelta; - } - - public int CompareTo(Mapping other) - { - if (Address < other.Address) - { - return -1; - } - else if (Address <= other.EndAddress - 1UL) - { - return 0; - } - else - { - return 1; - } - } - } - - private class PrivateMapping : IntrusiveRedBlackTreeNode, IComparable - { - public ulong Address { get; private set; } - public ulong Size { get; private set; } - public ulong EndAddress => Address + Size; - public PrivateMemoryAllocation PrivateAllocation { get; private set; } - - public PrivateMapping(ulong address, ulong size, PrivateMemoryAllocation privateAllocation) - { - Address = address; - Size = size; - PrivateAllocation = privateAllocation; - } - - public PrivateMapping Split(ulong splitAddress) - { - ulong leftSize = splitAddress - Address; - ulong rightSize = EndAddress - splitAddress; - - (var leftAllocation, PrivateAllocation) = PrivateAllocation.Split(leftSize); - - PrivateMapping left = new(Address, leftSize, leftAllocation); - - Address = splitAddress; - Size = rightSize; - - return left; - } - - public void Map(MemoryBlock baseBlock, MemoryBlock mirrorBlock, PrivateMemoryAllocation newAllocation) - { - baseBlock.MapView(newAllocation.Memory, newAllocation.Offset, Address, Size); - mirrorBlock.MapView(newAllocation.Memory, newAllocation.Offset, Address, Size); - PrivateAllocation = newAllocation; - } - - public void Unmap(MemoryBlock baseBlock, MemoryBlock mirrorBlock) - { - if (PrivateAllocation.IsValid) - { - baseBlock.UnmapView(PrivateAllocation.Memory, Address, Size); - mirrorBlock.UnmapView(PrivateAllocation.Memory, Address, Size); - PrivateAllocation.Dispose(); - } - - PrivateAllocation = default; - } - - public void Extend(ulong sizeDelta) - { - Size += sizeDelta; - } - - public int CompareTo(PrivateMapping other) - { - if (Address < other.Address) - { - return -1; - } - else if (Address <= other.EndAddress - 1UL) - { - return 0; - } - else - { - return 1; - } - } - } - private readonly MemoryBlock _backingMemory; - private readonly PrivateMemoryAllocator _privateMemoryAllocator; - private readonly IntrusiveRedBlackTree _mappingTree; - private readonly IntrusiveRedBlackTree _privateTree; - - private readonly object _treeLock; - - private readonly bool _supports4KBPages; public MemoryBlock Base { get; } public MemoryBlock Mirror { get; } public ulong AddressSpaceSize { get; } - public AddressSpace(MemoryBlock backingMemory, MemoryBlock baseMemory, MemoryBlock mirrorMemory, ulong addressSpaceSize, bool supports4KBPages) + public AddressSpace(MemoryBlock backingMemory, MemoryBlock baseMemory, MemoryBlock mirrorMemory, ulong addressSpaceSize) { - if (!supports4KBPages) - { - _privateMemoryAllocator = new PrivateMemoryAllocator(DefaultBlockAlignment, MemoryAllocationFlags.Mirrorable | MemoryAllocationFlags.NoMap); - _mappingTree = new IntrusiveRedBlackTree(); - _privateTree = new IntrusiveRedBlackTree(); - _treeLock = new object(); - - _mappingTree.Add(new Mapping(0UL, addressSpaceSize, MappingType.None)); - _privateTree.Add(new PrivateMapping(0UL, addressSpaceSize, default)); - } - _backingMemory = backingMemory; - _supports4KBPages = supports4KBPages; Base = baseMemory; Mirror = mirrorMemory; AddressSpaceSize = addressSpaceSize; } - public static bool TryCreate(MemoryBlock backingMemory, ulong asSize, bool supports4KBPages, out AddressSpace addressSpace) + public static bool TryCreate(MemoryBlock backingMemory, ulong asSize, out AddressSpace addressSpace) { addressSpace = null; @@ -193,7 +39,7 @@ namespace Ryujinx.Cpu { baseMemory = new MemoryBlock(addressSpaceSize, AsFlags); mirrorMemory = new MemoryBlock(addressSpaceSize, AsFlags); - addressSpace = new AddressSpace(backingMemory, baseMemory, mirrorMemory, addressSpaceSize, supports4KBPages); + addressSpace = new AddressSpace(backingMemory, baseMemory, mirrorMemory, addressSpaceSize); break; } @@ -209,289 +55,20 @@ namespace Ryujinx.Cpu public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags) { - if (_supports4KBPages) - { - Base.MapView(_backingMemory, pa, va, size); - Mirror.MapView(_backingMemory, pa, va, size); - - return; - } - - lock (_treeLock) - { - ulong alignment = MemoryBlock.GetPageSize(); - bool isAligned = ((va | pa | size) & (alignment - 1)) == 0; - - if (flags.HasFlag(MemoryMapFlags.Private) && !isAligned) - { - Update(va, pa, size, MappingType.Private); - } - else - { - // The update method assumes that shared mappings are already aligned. - - if (!flags.HasFlag(MemoryMapFlags.Private)) - { - if ((va & (alignment - 1)) != (pa & (alignment - 1))) - { - throw new InvalidMemoryRegionException($"Virtual address 0x{va:X} and physical address 0x{pa:X} are misaligned and can't be aligned."); - } - - ulong endAddress = va + size; - va = BitUtils.AlignDown(va, alignment); - pa = BitUtils.AlignDown(pa, alignment); - size = BitUtils.AlignUp(endAddress, alignment) - va; - } - - Update(va, pa, size, MappingType.Shared); - } - } + Base.MapView(_backingMemory, pa, va, size); + Mirror.MapView(_backingMemory, pa, va, size); } public void Unmap(ulong va, ulong size) { - if (_supports4KBPages) - { - Base.UnmapView(_backingMemory, va, size); - Mirror.UnmapView(_backingMemory, va, size); - - return; - } - - lock (_treeLock) - { - Update(va, 0UL, size, MappingType.None); - } - } - - private void Update(ulong va, ulong pa, ulong size, MappingType type) - { - Mapping map = _mappingTree.GetNode(new Mapping(va, 1UL, MappingType.None)); - - Update(map, va, pa, size, type); - } - - private Mapping Update(Mapping map, ulong va, ulong pa, ulong size, MappingType type) - { - ulong endAddress = va + size; - - for (; map != null; map = map.Successor) - { - if (map.Address < va) - { - _mappingTree.Add(map.Split(va)); - } - - if (map.EndAddress > endAddress) - { - Mapping newMap = map.Split(endAddress); - _mappingTree.Add(newMap); - map = newMap; - } - - switch (type) - { - case MappingType.None: - if (map.Type == MappingType.Shared) - { - ulong startOffset = map.Address - va; - ulong mapVa = va + startOffset; - ulong mapSize = Math.Min(size - startOffset, map.Size); - ulong mapEndAddress = mapVa + mapSize; - ulong alignment = MemoryBlock.GetPageSize(); - - mapVa = BitUtils.AlignDown(mapVa, alignment); - mapEndAddress = BitUtils.AlignUp(mapEndAddress, alignment); - - mapSize = mapEndAddress - mapVa; - - Base.UnmapView(_backingMemory, mapVa, mapSize); - Mirror.UnmapView(_backingMemory, mapVa, mapSize); - } - else - { - UnmapPrivate(va, size); - } - break; - case MappingType.Private: - if (map.Type == MappingType.Shared) - { - throw new InvalidMemoryRegionException($"Private mapping request at 0x{va:X} with size 0x{size:X} overlaps shared mapping at 0x{map.Address:X} with size 0x{map.Size:X}."); - } - else - { - MapPrivate(va, size); - } - break; - case MappingType.Shared: - if (map.Type != MappingType.None) - { - throw new InvalidMemoryRegionException($"Shared mapping request at 0x{va:X} with size 0x{size:X} overlaps mapping at 0x{map.Address:X} with size 0x{map.Size:X}."); - } - else - { - ulong startOffset = map.Address - va; - ulong mapPa = pa + startOffset; - ulong mapVa = va + startOffset; - ulong mapSize = Math.Min(size - startOffset, map.Size); - - Base.MapView(_backingMemory, mapPa, mapVa, mapSize); - Mirror.MapView(_backingMemory, mapPa, mapVa, mapSize); - } - break; - } - - map.UpdateState(type); - map = TryCoalesce(map); - - if (map.EndAddress >= endAddress) - { - break; - } - } - - return map; - } - - private Mapping TryCoalesce(Mapping map) - { - Mapping previousMap = map.Predecessor; - Mapping nextMap = map.Successor; - - if (previousMap != null && CanCoalesce(previousMap, map)) - { - previousMap.Extend(map.Size); - _mappingTree.Remove(map); - map = previousMap; - } - - if (nextMap != null && CanCoalesce(map, nextMap)) - { - map.Extend(nextMap.Size); - _mappingTree.Remove(nextMap); - } - - return map; - } - - private static bool CanCoalesce(Mapping left, Mapping right) - { - return left.Type == right.Type; - } - - private void MapPrivate(ulong va, ulong size) - { - ulong endAddress = va + size; - - ulong alignment = MemoryBlock.GetPageSize(); - - // Expand the range outwards based on page size to ensure that at least the requested region is mapped. - ulong vaAligned = BitUtils.AlignDown(va, alignment); - ulong endAddressAligned = BitUtils.AlignUp(endAddress, alignment); - - PrivateMapping map = _privateTree.GetNode(new PrivateMapping(va, 1UL, default)); - - for (; map != null; map = map.Successor) - { - if (!map.PrivateAllocation.IsValid) - { - if (map.Address < vaAligned) - { - _privateTree.Add(map.Split(vaAligned)); - } - - if (map.EndAddress > endAddressAligned) - { - PrivateMapping newMap = map.Split(endAddressAligned); - _privateTree.Add(newMap); - map = newMap; - } - - map.Map(Base, Mirror, _privateMemoryAllocator.Allocate(map.Size, MemoryBlock.GetPageSize())); - } - - if (map.EndAddress >= endAddressAligned) - { - break; - } - } - } - - private void UnmapPrivate(ulong va, ulong size) - { - ulong endAddress = va + size; - - ulong alignment = MemoryBlock.GetPageSize(); - - // Shrink the range inwards based on page size to ensure we won't unmap memory that might be still in use. - ulong vaAligned = BitUtils.AlignUp(va, alignment); - ulong endAddressAligned = BitUtils.AlignDown(endAddress, alignment); - - if (endAddressAligned <= vaAligned) - { - return; - } - - PrivateMapping map = _privateTree.GetNode(new PrivateMapping(va, 1UL, default)); - - for (; map != null; map = map.Successor) - { - if (map.PrivateAllocation.IsValid) - { - if (map.Address < vaAligned) - { - _privateTree.Add(map.Split(vaAligned)); - } - - if (map.EndAddress > endAddressAligned) - { - PrivateMapping newMap = map.Split(endAddressAligned); - _privateTree.Add(newMap); - map = newMap; - } - - map.Unmap(Base, Mirror); - map = TryCoalesce(map); - } - - if (map.EndAddress >= endAddressAligned) - { - break; - } - } - } - - private PrivateMapping TryCoalesce(PrivateMapping map) - { - PrivateMapping previousMap = map.Predecessor; - PrivateMapping nextMap = map.Successor; - - if (previousMap != null && CanCoalesce(previousMap, map)) - { - previousMap.Extend(map.Size); - _privateTree.Remove(map); - map = previousMap; - } - - if (nextMap != null && CanCoalesce(map, nextMap)) - { - map.Extend(nextMap.Size); - _privateTree.Remove(nextMap); - } - - return map; - } - - private static bool CanCoalesce(PrivateMapping left, PrivateMapping right) - { - return !left.PrivateAllocation.IsValid && !right.PrivateAllocation.IsValid; + Base.UnmapView(_backingMemory, va, size); + Mirror.UnmapView(_backingMemory, va, size); } public void Dispose() { GC.SuppressFinalize(this); - _privateMemoryAllocator?.Dispose(); Base.Dispose(); Mirror.Dispose(); } diff --git a/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs b/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs index 0c2e5f33a0..abdddb31c3 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs @@ -28,7 +28,7 @@ namespace Ryujinx.Cpu.AppleHv private readonly ManagedPageFlags _pages; - public bool Supports4KBPages => true; + public bool UsesPrivateAllocations => false; public int AddressSpaceBits { get; } diff --git a/src/Ryujinx.Cpu/Jit/MemoryManager.cs b/src/Ryujinx.Cpu/Jit/MemoryManager.cs index dfa5b93539..6f594ec2fd 100644 --- a/src/Ryujinx.Cpu/Jit/MemoryManager.cs +++ b/src/Ryujinx.Cpu/Jit/MemoryManager.cs @@ -25,7 +25,7 @@ namespace Ryujinx.Cpu.Jit private readonly InvalidAccessHandler _invalidAccessHandler; /// - public bool Supports4KBPages => true; + public bool UsesPrivateAllocations => false; /// /// Address space width in bits. diff --git a/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs b/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs index c60ab6b246..4639ab913d 100644 --- a/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs +++ b/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs @@ -27,7 +27,7 @@ namespace Ryujinx.Cpu.Jit private readonly ManagedPageFlags _pages; /// - public bool Supports4KBPages => MemoryBlock.GetPageSize() == PageSize; + public bool UsesPrivateAllocations => false; public int AddressSpaceBits { get; } diff --git a/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs b/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs index b2964cd29c..6dffcd295b 100644 --- a/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs +++ b/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs @@ -33,7 +33,7 @@ namespace Ryujinx.Cpu.Jit protected override ulong AddressSpaceSize { get; } /// - public bool Supports4KBPages => false; + public bool UsesPrivateAllocations => true; public IntPtr PageTablePointer => _nativePageTable.PageTablePointer; diff --git a/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs b/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs index e8c433269e..6646826cb0 100644 --- a/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs +++ b/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs @@ -75,7 +75,7 @@ namespace Ryujinx.HLE.HOS // We want to use host tracked mode if the host page size is > 4KB. if ((mode == MemoryManagerMode.HostMapped || mode == MemoryManagerMode.HostMappedUnsafe) && MemoryBlock.GetPageSize() <= 0x1000) { - if (!AddressSpace.TryCreate(context.Memory, addressSpaceSize, MemoryBlock.GetPageSize() == MemoryManagerHostMapped.PageSize, out addressSpace)) + if (!AddressSpace.TryCreate(context.Memory, addressSpaceSize, out addressSpace)) { Logger.Warning?.Print(LogClass.Cpu, "Address space creation failed, falling back to software page table"); diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs index d7b601d1c5..d262c159d7 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs @@ -11,7 +11,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory { private readonly IVirtualMemoryManager _cpuMemory; - protected override bool Supports4KBPages => _cpuMemory.Supports4KBPages; + protected override bool UsesPrivateAllocations => _cpuMemory.UsesPrivateAllocations; public KPageTable(KernelContext context, IVirtualMemoryManager cpuMemory, ulong reservedAddressSpaceSize) : base(context, reservedAddressSpaceSize) { diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs index 6470742d92..ae99a434ad 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs @@ -32,7 +32,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory private const int MaxBlocksNeededForInsertion = 2; protected readonly KernelContext Context; - protected virtual bool Supports4KBPages => true; + protected virtual bool UsesPrivateAllocations => false; public ulong AddrSpaceStart { get; private set; } public ulong AddrSpaceEnd { get; private set; } @@ -1947,17 +1947,17 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory Result result; - if (srcPageTable.Supports4KBPages) + if (srcPageTable.UsesPrivateAllocations) + { + result = MapForeign(srcPageTable.GetHostRegions(addressRounded, alignedSize), currentVa, alignedSize); + } + else { KPageList pageList = new(); srcPageTable.GetPhysicalRegions(addressRounded, alignedSize, pageList); result = MapPages(currentVa, pageList, permission, MemoryMapFlags.None); } - else - { - result = MapForeign(srcPageTable.GetHostRegions(addressRounded, alignedSize), currentVa, alignedSize); - } if (result != Result.Success) { diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs index e302ee4434..e593a7e13f 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs @@ -2,7 +2,6 @@ using Ryujinx.Common; using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.Horizon.Common; -using Ryujinx.Memory; namespace Ryujinx.HLE.HOS.Kernel.Memory { @@ -49,17 +48,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory return KernelResult.InvalidPermission; } - // On platforms with page size > 4 KB, this can fail due to the address not being page aligned, - // we can return an error to force the application to retry with a different address. - - try - { - return memoryManager.MapPages(address, _pageList, MemoryState.SharedMemory, permission); - } - catch (InvalidMemoryRegionException) - { - return KernelResult.InvalidMemState; - } + return memoryManager.MapPages(address, _pageList, MemoryState.SharedMemory, permission); } public Result UnmapFromProcess(KPageTableBase memoryManager, ulong address, ulong size, KProcess process) diff --git a/src/Ryujinx.Memory/AddressSpaceManager.cs b/src/Ryujinx.Memory/AddressSpaceManager.cs index f089c85736..807c5c0f40 100644 --- a/src/Ryujinx.Memory/AddressSpaceManager.cs +++ b/src/Ryujinx.Memory/AddressSpaceManager.cs @@ -13,7 +13,7 @@ namespace Ryujinx.Memory public sealed class AddressSpaceManager : VirtualMemoryManagerBase, IVirtualMemoryManager { /// - public bool Supports4KBPages => true; + public bool UsesPrivateAllocations => false; /// /// Address space width in bits. diff --git a/src/Ryujinx.Memory/IVirtualMemoryManager.cs b/src/Ryujinx.Memory/IVirtualMemoryManager.cs index 96d3e85797..102cedc94e 100644 --- a/src/Ryujinx.Memory/IVirtualMemoryManager.cs +++ b/src/Ryujinx.Memory/IVirtualMemoryManager.cs @@ -8,10 +8,10 @@ namespace Ryujinx.Memory public interface IVirtualMemoryManager { /// - /// Indicates whenever the memory manager supports aliasing pages at 4KB granularity. + /// Indicates whether the memory manager creates private allocations when the flag is set on map. /// - /// True if 4KB pages are supported by the memory manager, false otherwise - bool Supports4KBPages { get; } + /// True if private mappings might be used, false otherwise + bool UsesPrivateAllocations { get; } /// /// Maps a virtual memory range into a physical memory range. diff --git a/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs b/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs index 15e7d9b89a..3fe44db218 100644 --- a/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs +++ b/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs @@ -8,7 +8,7 @@ namespace Ryujinx.Tests.Memory { public class MockVirtualMemoryManager : IVirtualMemoryManager { - public bool Supports4KBPages => true; + public bool UsesPrivateAllocations => false; public bool NoMappings = false; From 451a28afb80996a7859659befaa602fe2db5c119 Mon Sep 17 00:00:00 2001 From: MutantAura <44103205+MutantAura@users.noreply.github.com> Date: Sat, 6 Apr 2024 18:58:02 +0100 Subject: [PATCH 34/87] misc: Add ANGLE configuration option to JSON and CLI (#6520) * Add hardware-acceleration toggle to ConfigurationState. * Add command line override for easier RenderDoc use. * Adjust CLI arguments. * fix whitespace format check * fix copypasta in comment * Add X11 rendering mode toggle * Remove ANGLE specific comments. --- .../Configuration/ConfigurationFileFormat.cs | 7 ++++++- .../Configuration/ConfigurationState.cs | 18 ++++++++++++++++++ .../Helper/CommandLineState.cs | 7 +++++++ src/Ryujinx/Program.cs | 14 ++++++++++++-- 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs index 0f6c21ef24..3387e1be13 100644 --- a/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs @@ -15,7 +15,7 @@ namespace Ryujinx.UI.Common.Configuration /// /// The current version of the file format /// - public const int CurrentVersion = 49; + public const int CurrentVersion = 50; /// /// Version of the configuration file format @@ -162,6 +162,11 @@ namespace Ryujinx.UI.Common.Configuration /// public bool ShowConfirmExit { get; set; } + /// + /// Enables hardware-accelerated rendering for Avalonia + /// + public bool EnableHardwareAcceleration { get; set; } + /// /// Whether to hide cursor on idle, always or never /// diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs index b7f36087cb..2609dc9baa 100644 --- a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs @@ -626,6 +626,11 @@ namespace Ryujinx.UI.Common.Configuration /// public ReactiveObject ShowConfirmExit { get; private set; } + /// + /// Enables hardware-accelerated rendering for Avalonia + /// + public ReactiveObject EnableHardwareAcceleration { get; private set; } + /// /// Hide Cursor on Idle /// @@ -642,6 +647,7 @@ namespace Ryujinx.UI.Common.Configuration EnableDiscordIntegration = new ReactiveObject(); CheckUpdatesOnStart = new ReactiveObject(); ShowConfirmExit = new ReactiveObject(); + EnableHardwareAcceleration = new ReactiveObject(); HideCursor = new ReactiveObject(); } @@ -678,6 +684,7 @@ namespace Ryujinx.UI.Common.Configuration EnableDiscordIntegration = EnableDiscordIntegration, CheckUpdatesOnStart = CheckUpdatesOnStart, ShowConfirmExit = ShowConfirmExit, + EnableHardwareAcceleration = EnableHardwareAcceleration, HideCursor = HideCursor, EnableVsync = Graphics.EnableVsync, EnableShaderCache = Graphics.EnableShaderCache, @@ -785,6 +792,7 @@ namespace Ryujinx.UI.Common.Configuration EnableDiscordIntegration.Value = true; CheckUpdatesOnStart.Value = true; ShowConfirmExit.Value = true; + EnableHardwareAcceleration.Value = true; HideCursor.Value = HideCursorMode.OnIdle; Graphics.EnableVsync.Value = true; Graphics.EnableShaderCache.Value = true; @@ -1442,6 +1450,15 @@ namespace Ryujinx.UI.Common.Configuration configurationFileUpdated = true; } + if (configurationFileFormat.Version < 50) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 50."); + + configurationFileFormat.EnableHardwareAcceleration = true; + + configurationFileUpdated = true; + } + Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog; Graphics.ResScale.Value = configurationFileFormat.ResScale; Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom; @@ -1472,6 +1489,7 @@ namespace Ryujinx.UI.Common.Configuration EnableDiscordIntegration.Value = configurationFileFormat.EnableDiscordIntegration; CheckUpdatesOnStart.Value = configurationFileFormat.CheckUpdatesOnStart; ShowConfirmExit.Value = configurationFileFormat.ShowConfirmExit; + EnableHardwareAcceleration.Value = configurationFileFormat.EnableHardwareAcceleration; HideCursor.Value = configurationFileFormat.HideCursor; Graphics.EnableVsync.Value = configurationFileFormat.EnableVsync; Graphics.EnableShaderCache.Value = configurationFileFormat.EnableShaderCache; diff --git a/src/Ryujinx.UI.Common/Helper/CommandLineState.cs b/src/Ryujinx.UI.Common/Helper/CommandLineState.cs index c3c5bd37e9..6de963a74a 100644 --- a/src/Ryujinx.UI.Common/Helper/CommandLineState.cs +++ b/src/Ryujinx.UI.Common/Helper/CommandLineState.cs @@ -8,6 +8,7 @@ namespace Ryujinx.UI.Common.Helper public static string[] Arguments { get; private set; } public static bool? OverrideDockedMode { get; private set; } + public static bool? OverrideHardwareAcceleration { get; private set; } public static string OverrideGraphicsBackend { get; private set; } public static string OverrideHideCursor { get; private set; } public static string BaseDirPathArg { get; private set; } @@ -87,6 +88,12 @@ namespace Ryujinx.UI.Common.Helper OverrideHideCursor = args[++i]; break; + case "--software-gui": + OverrideHardwareAcceleration = false; + break; + case "--hardware-gui": + OverrideHardwareAcceleration = true; + break; default: LaunchPathArg = arg; break; diff --git a/src/Ryujinx/Program.cs b/src/Ryujinx/Program.cs index aecc585fcb..4a30aee9ca 100644 --- a/src/Ryujinx/Program.cs +++ b/src/Ryujinx/Program.cs @@ -60,12 +60,16 @@ namespace Ryujinx.Ava EnableMultiTouch = true, EnableIme = true, EnableInputFocusProxy = Environment.GetEnvironmentVariable("XDG_CURRENT_DESKTOP") == "gamescope", - RenderingMode = new[] { X11RenderingMode.Glx, X11RenderingMode.Software }, + RenderingMode = ConfigurationState.Instance.EnableHardwareAcceleration ? + new[] { X11RenderingMode.Glx, X11RenderingMode.Software } : + new[] { X11RenderingMode.Software }, }) .With(new Win32PlatformOptions { WinUICompositionBackdropCornerRadius = 8.0f, - RenderingMode = new[] { Win32RenderingMode.AngleEgl, Win32RenderingMode.Software }, + RenderingMode = ConfigurationState.Instance.EnableHardwareAcceleration ? + new[] { Win32RenderingMode.AngleEgl, Win32RenderingMode.Software } : + new[] { Win32RenderingMode.Software }, }) .UseSkia(); } @@ -191,6 +195,12 @@ namespace Ryujinx.Ava _ => ConfigurationState.Instance.HideCursor.Value, }; } + + // Check if hardware-acceleration was overridden. + if (CommandLineState.OverrideHardwareAcceleration != null) + { + ConfigurationState.Instance.EnableHardwareAcceleration.Value = CommandLineState.OverrideHardwareAcceleration.Value; + } } private static void PrintSystemInfo() From 0b55914864f225236ddbe4f4e80ad6c7eb5d2c01 Mon Sep 17 00:00:00 2001 From: Marco Carvalho Date: Sun, 7 Apr 2024 16:49:14 -0300 Subject: [PATCH 35/87] Replacing the try-catch block with null-conditional and null-coalescing operators (#6612) * Replacing the try-catch block with null-conditional and null-coalescing operators * repeating --- src/Ryujinx.HLE/HOS/ModLoader.cs | 28 ++++------------------------ 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/src/Ryujinx.HLE/HOS/ModLoader.cs b/src/Ryujinx.HLE/HOS/ModLoader.cs index de9d2a5039..ee179c9290 100644 --- a/src/Ryujinx.HLE/HOS/ModLoader.cs +++ b/src/Ryujinx.HLE/HOS/ModLoader.cs @@ -173,36 +173,16 @@ namespace Ryujinx.HLE.HOS if (StrEquals(RomfsDir, modDir.Name)) { - bool enabled; - - try - { - var modData = modMetadata.Mods.Find(x => modDir.FullName.Contains(x.Path)); - enabled = modData.Enabled; - } - catch - { - // Mod is not in the list yet. New mods should be enabled by default. - enabled = true; - } + var modData = modMetadata.Mods.Find(x => modDir.FullName.Contains(x.Path)); + var enabled = modData?.Enabled ?? true; mods.RomfsDirs.Add(mod = new Mod(dir.Name, modDir, enabled)); types.Append('R'); } else if (StrEquals(ExefsDir, modDir.Name)) { - bool enabled; - - try - { - var modData = modMetadata.Mods.Find(x => modDir.FullName.Contains(x.Path)); - enabled = modData.Enabled; - } - catch - { - // Mod is not in the list yet. New mods should be enabled by default. - enabled = true; - } + var modData = modMetadata.Mods.Find(x => modDir.FullName.Contains(x.Path)); + var enabled = modData?.Enabled ?? true; mods.ExefsDirs.Add(mod = new Mod(dir.Name, modDir, enabled)); types.Append('E'); From 3e0d67533f1d31f9d0d0ac0b48922c719c5d8424 Mon Sep 17 00:00:00 2001 From: Marco Carvalho Date: Sun, 7 Apr 2024 17:55:34 -0300 Subject: [PATCH 36/87] Enhance Error Handling with Try-Pattern Refactoring (#6610) * Enhance Error Handling with Try-Pattern Refactoring * refactoring * refactoring * Update src/Ryujinx.HLE/FileSystem/ContentPath.cs Co-authored-by: gdkchan --------- Co-authored-by: gdkchan --- src/Ryujinx.HLE/FileSystem/ContentManager.cs | 21 ++++++++------------ src/Ryujinx.HLE/FileSystem/ContentPath.cs | 16 +++++++++------ 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/Ryujinx.HLE/FileSystem/ContentManager.cs b/src/Ryujinx.HLE/FileSystem/ContentManager.cs index b27eb5ead4..3c34a886ba 100644 --- a/src/Ryujinx.HLE/FileSystem/ContentManager.cs +++ b/src/Ryujinx.HLE/FileSystem/ContentManager.cs @@ -104,20 +104,15 @@ namespace Ryujinx.HLE.FileSystem foreach (StorageId storageId in Enum.GetValues()) { - string contentDirectory = null; - string contentPathString = null; - string registeredDirectory = null; - - try - { - contentPathString = ContentPath.GetContentPath(storageId); - contentDirectory = ContentPath.GetRealPath(contentPathString); - registeredDirectory = Path.Combine(contentDirectory, "registered"); - } - catch (NotSupportedException) + if (!ContentPath.TryGetContentPath(storageId, out var contentPathString)) { continue; } + if (!ContentPath.TryGetRealPath(contentPathString, out var contentDirectory)) + { + continue; + } + var registeredDirectory = Path.Combine(contentDirectory, "registered"); Directory.CreateDirectory(registeredDirectory); @@ -471,8 +466,8 @@ namespace Ryujinx.HLE.FileSystem public void InstallFirmware(string firmwareSource) { - string contentPathString = ContentPath.GetContentPath(StorageId.BuiltInSystem); - string contentDirectory = ContentPath.GetRealPath(contentPathString); + ContentPath.TryGetContentPath(StorageId.BuiltInSystem, out var contentPathString); + ContentPath.TryGetRealPath(contentPathString, out var contentDirectory); string registeredDirectory = Path.Combine(contentDirectory, "registered"); string temporaryDirectory = Path.Combine(contentDirectory, "temp"); diff --git a/src/Ryujinx.HLE/FileSystem/ContentPath.cs b/src/Ryujinx.HLE/FileSystem/ContentPath.cs index 02539c5c64..ffc212f771 100644 --- a/src/Ryujinx.HLE/FileSystem/ContentPath.cs +++ b/src/Ryujinx.HLE/FileSystem/ContentPath.cs @@ -26,17 +26,19 @@ namespace Ryujinx.HLE.FileSystem public const string Nintendo = "Nintendo"; public const string Contents = "Contents"; - public static string GetRealPath(string switchContentPath) + public static bool TryGetRealPath(string switchContentPath, out string realPath) { - return switchContentPath switch + realPath = switchContentPath switch { SystemContent => Path.Combine(AppDataManager.BaseDirPath, SystemNandPath, Contents), UserContent => Path.Combine(AppDataManager.BaseDirPath, UserNandPath, Contents), SdCardContent => Path.Combine(GetSdCardPath(), Nintendo, Contents), System => Path.Combine(AppDataManager.BaseDirPath, SystemNandPath), User => Path.Combine(AppDataManager.BaseDirPath, UserNandPath), - _ => throw new NotSupportedException($"Content Path \"`{switchContentPath}`\" is not supported."), + _ => null, }; + + return realPath != null; } public static string GetContentPath(ContentStorageId contentStorageId) @@ -50,15 +52,17 @@ namespace Ryujinx.HLE.FileSystem }; } - public static string GetContentPath(StorageId storageId) + public static bool TryGetContentPath(StorageId storageId, out string contentPath) { - return storageId switch + contentPath = storageId switch { StorageId.BuiltInSystem => SystemContent, StorageId.BuiltInUser => UserContent, StorageId.SdCard => SdCardContent, - _ => throw new NotSupportedException($"Storage Id \"`{storageId}`\" is not supported."), + _ => null, }; + + return contentPath != null; } public static StorageId GetStorageId(string contentPathString) From ead9a251418bb2402ffa19ece089406b0678544e Mon Sep 17 00:00:00 2001 From: jhorv <38920027+jhorv@users.noreply.github.com> Date: Sun, 7 Apr 2024 17:07:32 -0400 Subject: [PATCH 37/87] Audio rendering: reduce memory allocations (#6604) * - WritableRegion: enable wrapping IMemoryOwner - IVirtualMemoryManager impls of GetWritableRegion() use pooled memory when region is non-contiguous. - IVirtualMemoryManager: add GetReadOnlySequence() and impls - ByteMemoryPool: add new method RentCopy() - ByteMemoryPool: make class static, remove ctor and singleton field from earlier impl * - BytesReadOnlySequenceSegment: move from Ryujinx.Common.Memory to Ryujinx.Memory - BytesReadOnlySequenceSegment: add IsContiguousWith() and Replace() methods - VirtualMemoryManagerBase: - remove generic type parameters, instead use ulong for virtual addresses and nuint for host/physical addresses - implement IWritableBlock - add virtual GetReadOnlySequence() with coalescing of contiguous segments - add virtual GetSpan() - add virtual GetWritableRegion() - add abstract IsMapped() - add virtual MapForeign(ulong, nuint, ulong) - add virtual Read() - add virtual Read(ulong, Span) - add virtual ReadTracked() - add virtual SignalMemoryTracking() - add virtual Write() - add virtual Write() - add virtual WriteUntracked() - add virtual WriteWithRedundancyCheck() - VirtualMemoryManagerRefCountedBase: remove generic type parameters - AddressSpaceManager: remove redundant methods, add required overrides - HvMemoryManager: remove redundant methods, add required overrides, add overrides for _invalidAccessHandler handling - MemoryManager: remove redundant methods, add required overrides, add overrides for _invalidAccessHandler handling - MemoryManagerHostMapped: remove redundant methods, add required overrides, add overrides for _invalidAccessHandler handling - NativeMemoryManager: add get properties for Pointer and Length - throughout: removed invalid comments * - WritableRegion: enable wrapping IMemoryOwner - IVirtualMemoryManager impls of GetWritableRegion() use pooled memory when region is non-contiguous. - IVirtualMemoryManager: add GetReadOnlySequence() and impls - ByteMemoryPool: add new method RentCopy() - ByteMemoryPool: make class static, remove ctor and singleton field from earlier impl * add PagedMemoryRange enumerator types, use them in IVirtualMemoryManager implementations to consolidate page-handling logic and add a new capability - the coalescing of pages for consolidating memory copies and segmentation. * new: more tests for PagedMemoryRangeCoalescingEnumerator showing coalescing of contiguous segments * make some struct properties readonly * put braces around `foreach` bodies * encourage inlining of some PagedMemoryRange*Enumerator members * DynamicRingBuffer: - use ByteMemoryPool - make some methods return without locking when size/count argument = 0 - make generic Read()/Write() non-generic because its only usage is as T = byte - change Read(byte[]...) to Read(Span...) - change Write(byte[]...) to Write(Span...) * change IAudioRenderer.RequestUpdate() to take a ReadOnlySequence, enabling zero-copy audio rendering * HipcGenerator: support ReadOnlySequence as IPC method parameter * change IAudioRenderer/AudioRenderer RequestUpdate* methods to take input as ReadOnlySequence * MemoryManagerHostTracked: use rented memory when contiguous in `GetWritableRegion()` * rebase cleanup * dotnet format fixes * format and comment fixes * format long parameter list - take 2 * - add support to HipcGenerator for buffers of type `Memory` - change `AudioRenderer` `RequestUpdate()` and `RequestUpdateAuto()` to use Memory for output buffers, removing another memory block allocation/copy * SplitterContext `UpdateState()` and `UpdateData()` smooth out advance/rewind logic, only rewind if magic is invalid * DynamicRingBuffer.Write(): change Span to ReadOnlySpan --- .../Backends/Common/DynamicRingBuffer.cs | 63 +-- .../Renderer/Common/BehaviourParameter.cs | 2 +- .../Renderer/Common/UpdateDataHeader.cs | 2 +- .../Parameter/BehaviourErrorInfoOutStatus.cs | 2 +- .../Renderer/Server/AudioRenderSystem.cs | 10 +- .../Renderer/Server/BehaviourContext.cs | 3 +- .../Server/Effect/AuxiliaryBufferEffect.cs | 14 +- .../Renderer/Server/Effect/BaseEffect.cs | 14 +- .../Server/Effect/BiquadFilterEffect.cs | 14 +- .../Renderer/Server/Effect/BufferMixEffect.cs | 14 +- .../Server/Effect/CaptureBufferEffect.cs | 14 +- .../Server/Effect/CompressorEffect.cs | 8 +- .../Renderer/Server/Effect/DelayEffect.cs | 14 +- .../Renderer/Server/Effect/LimiterEffect.cs | 14 +- .../Renderer/Server/Effect/Reverb3dEffect.cs | 14 +- .../Renderer/Server/Effect/ReverbEffect.cs | 14 +- .../Renderer/Server/MemoryPool/PoolMapper.cs | 2 +- .../Renderer/Server/Mix/MixState.cs | 6 +- .../Renderer/Server/Sink/BaseSink.cs | 8 +- .../Server/Sink/CircularBufferSink.cs | 6 +- .../Renderer/Server/Sink/DeviceSink.cs | 6 +- .../Server/Splitter/SplitterContext.cs | 55 +-- .../Renderer/Server/Splitter/SplitterState.cs | 11 +- .../Renderer/Server/StateUpdater.cs | 154 ++++---- .../Renderer/Server/Voice/VoiceState.cs | 26 +- .../Extensions/SequenceReaderExtensions.cs | 181 +++++++++ .../Jit/MemoryManagerHostTracked.cs | 8 +- .../Hipc/HipcGenerator.cs | 34 +- .../Sdk/Audio/Detail/AudioRenderer.cs | 26 +- .../Sdk/Audio/Detail/IAudioRenderer.cs | 5 +- .../Sdk/Sf/CommandSerialization.cs | 6 + .../SequenceReaderExtensionsTests.cs | 359 ++++++++++++++++++ 32 files changed, 847 insertions(+), 262 deletions(-) create mode 100644 src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs create mode 100644 src/Ryujinx.Tests/Common/Extensions/SequenceReaderExtensionsTests.cs diff --git a/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs b/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs index 05dd2162a5..b95e5bed14 100644 --- a/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs +++ b/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs @@ -1,5 +1,7 @@ using Ryujinx.Common; +using Ryujinx.Common.Memory; using System; +using System.Buffers; namespace Ryujinx.Audio.Backends.Common { @@ -12,7 +14,8 @@ namespace Ryujinx.Audio.Backends.Common private readonly object _lock = new(); - private byte[] _buffer; + private IMemoryOwner _bufferOwner; + private Memory _buffer; private int _size; private int _headOffset; private int _tailOffset; @@ -21,7 +24,8 @@ namespace Ryujinx.Audio.Backends.Common public DynamicRingBuffer(int initialCapacity = RingBufferAlignment) { - _buffer = new byte[initialCapacity]; + _bufferOwner = ByteMemoryPool.RentCleared(initialCapacity); + _buffer = _bufferOwner.Memory; } public void Clear() @@ -33,6 +37,11 @@ namespace Ryujinx.Audio.Backends.Common public void Clear(int size) { + if (size == 0) + { + return; + } + lock (_lock) { if (size > _size) @@ -40,11 +49,6 @@ namespace Ryujinx.Audio.Backends.Common size = _size; } - if (size == 0) - { - return; - } - _headOffset = (_headOffset + size) % _buffer.Length; _size -= size; @@ -58,28 +62,31 @@ namespace Ryujinx.Audio.Backends.Common private void SetCapacityLocked(int capacity) { - byte[] buffer = new byte[capacity]; + IMemoryOwner newBufferOwner = ByteMemoryPool.RentCleared(capacity); + Memory newBuffer = newBufferOwner.Memory; if (_size > 0) { if (_headOffset < _tailOffset) { - Buffer.BlockCopy(_buffer, _headOffset, buffer, 0, _size); + _buffer.Slice(_headOffset, _size).CopyTo(newBuffer); } else { - Buffer.BlockCopy(_buffer, _headOffset, buffer, 0, _buffer.Length - _headOffset); - Buffer.BlockCopy(_buffer, 0, buffer, _buffer.Length - _headOffset, _tailOffset); + _buffer[_headOffset..].CopyTo(newBuffer); + _buffer[.._tailOffset].CopyTo(newBuffer[(_buffer.Length - _headOffset)..]); } } - _buffer = buffer; + _bufferOwner.Dispose(); + + _bufferOwner = newBufferOwner; + _buffer = newBuffer; _headOffset = 0; _tailOffset = _size; } - - public void Write(T[] buffer, int index, int count) + public void Write(ReadOnlySpan buffer, int index, int count) { if (count == 0) { @@ -99,17 +106,17 @@ namespace Ryujinx.Audio.Backends.Common if (tailLength >= count) { - Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, count); + buffer.Slice(index, count).CopyTo(_buffer.Span[_tailOffset..]); } else { - Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, tailLength); - Buffer.BlockCopy(buffer, index + tailLength, _buffer, 0, count - tailLength); + buffer.Slice(index, tailLength).CopyTo(_buffer.Span[_tailOffset..]); + buffer.Slice(index + tailLength, count - tailLength).CopyTo(_buffer.Span); } } else { - Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, count); + buffer.Slice(index, count).CopyTo(_buffer.Span[_tailOffset..]); } _size += count; @@ -117,8 +124,13 @@ namespace Ryujinx.Audio.Backends.Common } } - public int Read(T[] buffer, int index, int count) + public int Read(Span buffer, int index, int count) { + if (count == 0) + { + return 0; + } + lock (_lock) { if (count > _size) @@ -126,14 +138,9 @@ namespace Ryujinx.Audio.Backends.Common count = _size; } - if (count == 0) - { - return 0; - } - if (_headOffset < _tailOffset) { - Buffer.BlockCopy(_buffer, _headOffset, buffer, index, count); + _buffer.Span.Slice(_headOffset, count).CopyTo(buffer[index..]); } else { @@ -141,12 +148,12 @@ namespace Ryujinx.Audio.Backends.Common if (tailLength >= count) { - Buffer.BlockCopy(_buffer, _headOffset, buffer, index, count); + _buffer.Span.Slice(_headOffset, count).CopyTo(buffer[index..]); } else { - Buffer.BlockCopy(_buffer, _headOffset, buffer, index, tailLength); - Buffer.BlockCopy(_buffer, 0, buffer, index + tailLength, count - tailLength); + _buffer.Span.Slice(_headOffset, tailLength).CopyTo(buffer[index..]); + _buffer.Span[..(count - tailLength)].CopyTo(buffer[(index + tailLength)..]); } } diff --git a/src/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs b/src/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs index b0963c9350..3b8d15dc53 100644 --- a/src/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs +++ b/src/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs @@ -25,7 +25,7 @@ namespace Ryujinx.Audio.Renderer.Common public ulong Flags; /// - /// Represents an error during . + /// Represents an error during . /// [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct ErrorInfo diff --git a/src/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs b/src/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs index 7efe3b02b4..98b224ebfb 100644 --- a/src/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs +++ b/src/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs @@ -4,7 +4,7 @@ using System.Runtime.CompilerServices; namespace Ryujinx.Audio.Renderer.Common { /// - /// Update data header used for input and output of . + /// Update data header used for input and output of . /// public struct UpdateDataHeader { diff --git a/src/Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs b/src/Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs index 5a0565dc61..72438be0e4 100644 --- a/src/Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs +++ b/src/Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs @@ -8,7 +8,7 @@ namespace Ryujinx.Audio.Renderer.Parameter /// /// Output information for behaviour. /// - /// This is used to report errors to the user during processing. + /// This is used to report errors to the user during processing. [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct BehaviourErrorInfoOutStatus { diff --git a/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs b/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs index 7bb8ae5ba7..9b56f5cbdf 100644 --- a/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs +++ b/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs @@ -386,7 +386,7 @@ namespace Ryujinx.Audio.Renderer.Server } } - public ResultCode Update(Memory output, Memory performanceOutput, ReadOnlyMemory input) + public ResultCode Update(Memory output, Memory performanceOutput, ReadOnlySequence input) { lock (_lock) { @@ -419,14 +419,16 @@ namespace Ryujinx.Audio.Renderer.Server return result; } - result = stateUpdater.UpdateVoices(_voiceContext, _memoryPools); + PoolMapper poolMapper = new PoolMapper(_processHandle, _memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled()); + + result = stateUpdater.UpdateVoices(_voiceContext, poolMapper); if (result != ResultCode.Success) { return result; } - result = stateUpdater.UpdateEffects(_effectContext, _isActive, _memoryPools); + result = stateUpdater.UpdateEffects(_effectContext, _isActive, poolMapper); if (result != ResultCode.Success) { @@ -450,7 +452,7 @@ namespace Ryujinx.Audio.Renderer.Server return result; } - result = stateUpdater.UpdateSinks(_sinkContext, _memoryPools); + result = stateUpdater.UpdateSinks(_sinkContext, poolMapper); if (result != ResultCode.Success) { diff --git a/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs b/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs index 3297b5d9fa..099d8f5619 100644 --- a/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs +++ b/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Diagnostics; using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; @@ -273,7 +274,7 @@ namespace Ryujinx.Audio.Renderer.Server } /// - /// Check if the audio renderer should trust the user destination count in . + /// Check if the audio renderer should trust the user destination count in . /// /// True if the audio renderer should trust the user destination count. public bool IsSplitterBugFixed() diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs index 57ca266f4d..74a9baff2a 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs @@ -33,21 +33,21 @@ namespace Ryujinx.Audio.Renderer.Server.Effect return WorkBuffers[index].GetReference(true); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); - UpdateParameterBase(ref parameter); + UpdateParameterBase(in parameter); Parameter = MemoryMarshal.Cast(parameter.SpecificData)[0]; IsEnabled = parameter.IsEnabled; diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs index a9716db2a5..77d9b5c295 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs @@ -81,7 +81,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect /// /// The user parameter. /// Returns true if the sent by the user matches the internal . - public bool IsTypeValid(ref T parameter) where T : unmanaged, IEffectInParameter + public bool IsTypeValid(in T parameter) where T : unmanaged, IEffectInParameter { return parameter.Type == TargetEffectType; } @@ -98,7 +98,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect /// Update the internal common parameters from a user parameter. /// /// The user parameter. - protected void UpdateParameterBase(ref T parameter) where T : unmanaged, IEffectInParameter + protected void UpdateParameterBase(in T parameter) where T : unmanaged, IEffectInParameter { MixId = parameter.MixId; ProcessingOrder = parameter.ProcessingOrder; @@ -139,7 +139,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect /// /// Initialize the given result state. /// - /// The state to initalize + /// The state to initialize public virtual void InitializeResultState(ref EffectResultState state) { } /// @@ -155,9 +155,9 @@ namespace Ryujinx.Audio.Renderer.Server.Effect /// The possible that was generated. /// The user parameter. /// The mapper to use. - public virtual void Update(out ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public virtual void Update(out ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); updateErrorInfo = new ErrorInfo(); } @@ -168,9 +168,9 @@ namespace Ryujinx.Audio.Renderer.Server.Effect /// The possible that was generated. /// The user parameter. /// The mapper to use. - public virtual void Update(out ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public virtual void Update(out ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); updateErrorInfo = new ErrorInfo(); } diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs index b987f7c85e..3b3e1021c4 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs @@ -35,21 +35,21 @@ namespace Ryujinx.Audio.Renderer.Server.Effect public override EffectType TargetEffectType => EffectType.BiquadFilter; - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); - UpdateParameterBase(ref parameter); + UpdateParameterBase(in parameter); Parameter = MemoryMarshal.Cast(parameter.SpecificData)[0]; IsEnabled = parameter.IsEnabled; diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs index d6cb9cfa39..5d82b5ae87 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs @@ -19,21 +19,21 @@ namespace Ryujinx.Audio.Renderer.Server.Effect public override EffectType TargetEffectType => EffectType.BufferMix; - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); - UpdateParameterBase(ref parameter); + UpdateParameterBase(in parameter); Parameter = MemoryMarshal.Cast(parameter.SpecificData)[0]; IsEnabled = parameter.IsEnabled; diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/CaptureBufferEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/CaptureBufferEffect.cs index 5be4b4ed51..6917222f0a 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/CaptureBufferEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/CaptureBufferEffect.cs @@ -32,21 +32,21 @@ namespace Ryujinx.Audio.Renderer.Server.Effect return WorkBuffers[index].GetReference(true); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); - UpdateParameterBase(ref parameter); + UpdateParameterBase(in parameter); Parameter = MemoryMarshal.Cast(parameter.SpecificData)[0]; IsEnabled = parameter.IsEnabled; diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs index 826c32cb07..eff60e7da8 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs @@ -39,17 +39,17 @@ namespace Ryujinx.Audio.Renderer.Server.Effect return GetSingleBuffer(); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { // Nintendo doesn't do anything here but we still require updateErrorInfo to be initialised. updateErrorInfo = new BehaviourParameter.ErrorInfo(); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); - UpdateParameterBase(ref parameter); + UpdateParameterBase(in parameter); Parameter = MemoryMarshal.Cast(parameter.SpecificData)[0]; IsEnabled = parameter.IsEnabled; diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs index 43cabb7db9..9db1ce465d 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs @@ -37,19 +37,19 @@ namespace Ryujinx.Audio.Renderer.Server.Effect return GetSingleBuffer(); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); ref DelayParameter delayParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; @@ -57,7 +57,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect if (delayParameter.IsChannelCountMaxValid()) { - UpdateParameterBase(ref parameter); + UpdateParameterBase(in parameter); UsageState oldParameterStatus = Parameter.Status; diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs index 3e2f7326d0..d9b3d5666e 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs @@ -39,25 +39,25 @@ namespace Ryujinx.Audio.Renderer.Server.Effect return GetSingleBuffer(); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); ref LimiterParameter limiterParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; updateErrorInfo = new BehaviourParameter.ErrorInfo(); - UpdateParameterBase(ref parameter); + UpdateParameterBase(in parameter); Parameter = limiterParameter; diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs index f9d7f4943c..4b13cfec62 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs @@ -36,19 +36,19 @@ namespace Ryujinx.Audio.Renderer.Server.Effect return GetSingleBuffer(); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); ref Reverb3dParameter reverbParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; @@ -56,7 +56,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect if (reverbParameter.IsChannelCountMaxValid()) { - UpdateParameterBase(ref parameter); + UpdateParameterBase(in parameter); UsageState oldParameterStatus = Parameter.ParameterStatus; diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs index 6fdf8fc23d..aa6e674481 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs @@ -39,19 +39,19 @@ namespace Ryujinx.Audio.Renderer.Server.Effect return GetSingleBuffer(); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); ref ReverbParameter reverbParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; @@ -59,7 +59,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect if (reverbParameter.IsChannelCountMaxValid()) { - UpdateParameterBase(ref parameter); + UpdateParameterBase(in parameter); UsageState oldParameterStatus = Parameter.Status; diff --git a/src/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs b/src/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs index 391b80f8db..f67d0c1249 100644 --- a/src/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs +++ b/src/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs @@ -249,7 +249,7 @@ namespace Ryujinx.Audio.Renderer.Server.MemoryPool /// Input user parameter. /// Output user parameter. /// Returns the of the operations performed. - public UpdateResult Update(ref MemoryPoolState memoryPool, ref MemoryPoolInParameter inParameter, ref MemoryPoolOutStatus outStatus) + public UpdateResult Update(ref MemoryPoolState memoryPool, in MemoryPoolInParameter inParameter, ref MemoryPoolOutStatus outStatus) { MemoryPoolUserState inputState = inParameter.State; diff --git a/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs b/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs index 88ae448314..b90574da92 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs @@ -195,7 +195,7 @@ namespace Ryujinx.Audio.Renderer.Server.Mix /// The input parameter of the mix. /// The splitter context. /// Return true, new connections were done on the adjacency matrix. - private bool UpdateConnection(EdgeMatrix edgeMatrix, ref MixParameter parameter, ref SplitterContext splitterContext) + private bool UpdateConnection(EdgeMatrix edgeMatrix, in MixParameter parameter, ref SplitterContext splitterContext) { bool hasNewConnections; @@ -259,7 +259,7 @@ namespace Ryujinx.Audio.Renderer.Server.Mix /// The splitter context. /// The behaviour context. /// Return true if the mix was changed. - public bool Update(EdgeMatrix edgeMatrix, ref MixParameter parameter, EffectContext effectContext, SplitterContext splitterContext, BehaviourContext behaviourContext) + public bool Update(EdgeMatrix edgeMatrix, in MixParameter parameter, EffectContext effectContext, SplitterContext splitterContext, BehaviourContext behaviourContext) { bool isDirty; @@ -273,7 +273,7 @@ namespace Ryujinx.Audio.Renderer.Server.Mix if (behaviourContext.IsSplitterSupported()) { - isDirty = UpdateConnection(edgeMatrix, ref parameter, ref splitterContext); + isDirty = UpdateConnection(edgeMatrix, in parameter, ref splitterContext); } else { diff --git a/src/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs b/src/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs index d36c5e260e..8c65e09bc3 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs @@ -59,7 +59,7 @@ namespace Ryujinx.Audio.Renderer.Server.Sink /// /// The user parameter. /// Return true, if the sent by the user match the internal . - public bool IsTypeValid(ref SinkInParameter parameter) + public bool IsTypeValid(in SinkInParameter parameter) { return parameter.Type == TargetSinkType; } @@ -76,7 +76,7 @@ namespace Ryujinx.Audio.Renderer.Server.Sink /// Update the internal common parameters from user parameter. /// /// The user parameter. - protected void UpdateStandardParameter(ref SinkInParameter parameter) + protected void UpdateStandardParameter(in SinkInParameter parameter) { if (IsUsed != parameter.IsUsed) { @@ -92,9 +92,9 @@ namespace Ryujinx.Audio.Renderer.Server.Sink /// The user parameter. /// The user output status. /// The mapper to use. - public virtual void Update(out ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) + public virtual void Update(out ErrorInfo errorInfo, in SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); errorInfo = new ErrorInfo(); } diff --git a/src/Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs b/src/Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs index 097757988d..f2751cf29b 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs @@ -44,18 +44,18 @@ namespace Ryujinx.Audio.Renderer.Server.Sink public override SinkType TargetSinkType => SinkType.CircularBuffer; - public override void Update(out BehaviourParameter.ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo errorInfo, in SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) { errorInfo = new BehaviourParameter.ErrorInfo(); outStatus = new SinkOutStatus(); - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); ref CircularBufferParameter inputDeviceParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; if (parameter.IsUsed != IsUsed || ShouldSkip) { - UpdateStandardParameter(ref parameter); + UpdateStandardParameter(in parameter); if (parameter.IsUsed) { diff --git a/src/Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs b/src/Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs index e03fe11d49..afe2d4b1b7 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs @@ -49,15 +49,15 @@ namespace Ryujinx.Audio.Renderer.Server.Sink public override SinkType TargetSinkType => SinkType.Device; - public override void Update(out BehaviourParameter.ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo errorInfo, in SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); ref DeviceParameter inputDeviceParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; if (parameter.IsUsed != IsUsed) { - UpdateStandardParameter(ref parameter); + UpdateStandardParameter(in parameter); Parameter = inputDeviceParameter; } else diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs index e408692ab9..3efa783c37 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs @@ -2,10 +2,11 @@ using Ryujinx.Audio.Renderer.Common; using Ryujinx.Audio.Renderer.Parameter; using Ryujinx.Audio.Renderer.Utils; using Ryujinx.Common; +using Ryujinx.Common.Extensions; using System; +using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; namespace Ryujinx.Audio.Renderer.Server.Splitter { @@ -25,7 +26,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter private Memory _splitterDestinations; /// - /// If set to true, trust the user destination count in . + /// If set to true, trust the user destination count in . /// public bool IsBugFixed { get; private set; } @@ -110,7 +111,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter ///
/// The storage. /// The storage. - /// If set to true, trust the user destination count in . + /// If set to true, trust the user destination count in . private void Setup(Memory splitters, Memory splitterDestinations, bool isBugFixed) { _splitters = splitters; @@ -148,11 +149,11 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter ///
/// The splitter header. /// The raw data after the splitter header. - private void UpdateState(scoped ref SplitterInParameterHeader inputHeader, ref ReadOnlySpan input) + private void UpdateState(in SplitterInParameterHeader inputHeader, ref SequenceReader input) { for (int i = 0; i < inputHeader.SplitterCount; i++) { - SplitterInParameter parameter = MemoryMarshal.Read(input); + ref readonly SplitterInParameter parameter = ref input.GetRefOrRefToCopy(out _); Debug.Assert(parameter.IsMagicValid()); @@ -162,10 +163,16 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter { ref SplitterState splitter = ref GetState(parameter.Id); - splitter.Update(this, ref parameter, input[Unsafe.SizeOf()..]); + splitter.Update(this, in parameter, ref input); } - input = input[(0x1C + parameter.DestinationCount * 4)..]; + // NOTE: there are 12 bytes of unused/unknown data after the destination IDs array. + input.Advance(0xC); + } + else + { + input.Rewind(Unsafe.SizeOf()); + break; } } } @@ -175,11 +182,11 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter ///
/// The splitter header. /// The raw data after the splitter header. - private void UpdateData(scoped ref SplitterInParameterHeader inputHeader, ref ReadOnlySpan input) + private void UpdateData(in SplitterInParameterHeader inputHeader, ref SequenceReader input) { for (int i = 0; i < inputHeader.SplitterDestinationCount; i++) { - SplitterDestinationInParameter parameter = MemoryMarshal.Read(input); + ref readonly SplitterDestinationInParameter parameter = ref input.GetRefOrRefToCopy(out _); Debug.Assert(parameter.IsMagicValid()); @@ -191,8 +198,11 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter destination.Update(parameter); } - - input = input[Unsafe.SizeOf()..]; + } + else + { + input.Rewind(Unsafe.SizeOf()); + break; } } } @@ -201,36 +211,33 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// Update splitter from user parameters. ///
/// The input raw user data. - /// The total consumed size. /// Return true if the update was successful. - public bool Update(ReadOnlySpan input, out int consumedSize) + public bool Update(ref SequenceReader input) { if (_splitterDestinations.IsEmpty || _splitters.IsEmpty) { - consumedSize = 0; - return true; } - int originalSize = input.Length; - - SplitterInParameterHeader header = SpanIOHelper.Read(ref input); + ref readonly SplitterInParameterHeader header = ref input.GetRefOrRefToCopy(out _); if (header.IsMagicValid()) { ClearAllNewConnectionFlag(); - UpdateState(ref header, ref input); - UpdateData(ref header, ref input); + UpdateState(in header, ref input); + UpdateData(in header, ref input); - consumedSize = BitUtils.AlignUp(originalSize - input.Length, 0x10); + input.SetConsumed(BitUtils.AlignUp(input.Consumed, 0x10)); return true; } + else + { + input.Rewind(Unsafe.SizeOf()); - consumedSize = 0; - - return false; + return false; + } } /// diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs index e08ee9ea77..109c81b23a 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs @@ -1,4 +1,5 @@ using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Common.Extensions; using System; using System.Buffers; using System.Diagnostics; @@ -122,7 +123,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// The splitter context. /// The user parameter. /// The raw input data after the . - public void Update(SplitterContext context, ref SplitterInParameter parameter, ReadOnlySpan input) + public void Update(SplitterContext context, in SplitterInParameter parameter, ref SequenceReader input) { ClearLinks(); @@ -139,9 +140,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter if (destinationCount > 0) { - ReadOnlySpan destinationIds = MemoryMarshal.Cast(input); + input.ReadLittleEndian(out int destinationId); - Memory destination = context.GetDestinationMemory(destinationIds[0]); + Memory destination = context.GetDestinationMemory(destinationId); SetDestination(ref destination.Span[0]); @@ -149,7 +150,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter for (int i = 1; i < destinationCount; i++) { - Memory nextDestination = context.GetDestinationMemory(destinationIds[i]); + input.ReadLittleEndian(out destinationId); + + Memory nextDestination = context.GetDestinationMemory(destinationId); destination.Span[0].Link(ref nextDestination.Span[0]); destination = nextDestination; diff --git a/src/Ryujinx.Audio/Renderer/Server/StateUpdater.cs b/src/Ryujinx.Audio/Renderer/Server/StateUpdater.cs index 22eebc7ccc..f8d87f2d14 100644 --- a/src/Ryujinx.Audio/Renderer/Server/StateUpdater.cs +++ b/src/Ryujinx.Audio/Renderer/Server/StateUpdater.cs @@ -9,41 +9,40 @@ using Ryujinx.Audio.Renderer.Server.Sink; using Ryujinx.Audio.Renderer.Server.Splitter; using Ryujinx.Audio.Renderer.Server.Voice; using Ryujinx.Audio.Renderer.Utils; +using Ryujinx.Common.Extensions; using Ryujinx.Common.Logging; using System; using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; namespace Ryujinx.Audio.Renderer.Server { - public class StateUpdater + public ref struct StateUpdater { - private readonly ReadOnlyMemory _inputOrigin; + private SequenceReader _inputReader; + private readonly ReadOnlyMemory _outputOrigin; - private ReadOnlyMemory _input; private Memory _output; private readonly uint _processHandle; private BehaviourContext _behaviourContext; - private UpdateDataHeader _inputHeader; + private readonly ref readonly UpdateDataHeader _inputHeader; private readonly Memory _outputHeader; - private ref UpdateDataHeader OutputHeader => ref _outputHeader.Span[0]; + private readonly ref UpdateDataHeader OutputHeader => ref _outputHeader.Span[0]; - public StateUpdater(ReadOnlyMemory input, Memory output, uint processHandle, BehaviourContext behaviourContext) + public StateUpdater(ReadOnlySequence input, Memory output, uint processHandle, BehaviourContext behaviourContext) { - _input = input; - _inputOrigin = _input; + _inputReader = new SequenceReader(input); _output = output; _outputOrigin = _output; _processHandle = processHandle; _behaviourContext = behaviourContext; - _inputHeader = SpanIOHelper.Read(ref _input); + _inputHeader = ref _inputReader.GetRefOrRefToCopy(out _); _outputHeader = SpanMemoryManager.Cast(_output[..Unsafe.SizeOf()]); OutputHeader.Initialize(_behaviourContext.UserRevision); @@ -52,7 +51,7 @@ namespace Ryujinx.Audio.Renderer.Server public ResultCode UpdateBehaviourContext() { - BehaviourParameter parameter = SpanIOHelper.Read(ref _input); + ref readonly BehaviourParameter parameter = ref _inputReader.GetRefOrRefToCopy(out _); if (!BehaviourContext.CheckValidRevision(parameter.UserRevision) || parameter.UserRevision != _behaviourContext.UserRevision) { @@ -81,11 +80,11 @@ namespace Ryujinx.Audio.Renderer.Server foreach (ref MemoryPoolState memoryPool in memoryPools) { - MemoryPoolInParameter parameter = SpanIOHelper.Read(ref _input); + ref readonly MemoryPoolInParameter parameter = ref _inputReader.GetRefOrRefToCopy(out _); ref MemoryPoolOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; - PoolMapper.UpdateResult updateResult = mapper.Update(ref memoryPool, ref parameter, ref outStatus); + PoolMapper.UpdateResult updateResult = mapper.Update(ref memoryPool, in parameter, ref outStatus); if (updateResult != PoolMapper.UpdateResult.Success && updateResult != PoolMapper.UpdateResult.MapError && @@ -115,7 +114,7 @@ namespace Ryujinx.Audio.Renderer.Server for (int i = 0; i < context.GetCount(); i++) { - VoiceChannelResourceInParameter parameter = SpanIOHelper.Read(ref _input); + ref readonly VoiceChannelResourceInParameter parameter = ref _inputReader.GetRefOrRefToCopy(out _); ref VoiceChannelResource resource = ref context.GetChannelResource(i); @@ -127,7 +126,7 @@ namespace Ryujinx.Audio.Renderer.Server return ResultCode.Success; } - public ResultCode UpdateVoices(VoiceContext context, Memory memoryPools) + public ResultCode UpdateVoices(VoiceContext context, PoolMapper mapper) { if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.VoicesSize) { @@ -136,11 +135,7 @@ namespace Ryujinx.Audio.Renderer.Server int initialOutputSize = _output.Length; - ReadOnlySpan parameters = MemoryMarshal.Cast(_input[..(int)_inputHeader.VoicesSize].Span); - - _input = _input[(int)_inputHeader.VoicesSize..]; - - PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled()); + long initialInputConsumed = _inputReader.Consumed; // First make everything not in use. for (int i = 0; i < context.GetCount(); i++) @@ -157,7 +152,7 @@ namespace Ryujinx.Audio.Renderer.Server // Start processing for (int i = 0; i < context.GetCount(); i++) { - VoiceInParameter parameter = parameters[i]; + ref readonly VoiceInParameter parameter = ref _inputReader.GetRefOrRefToCopy(out _); voiceUpdateStates.Fill(Memory.Empty); @@ -181,14 +176,14 @@ namespace Ryujinx.Audio.Renderer.Server currentVoiceState.Initialize(); } - currentVoiceState.UpdateParameters(out ErrorInfo updateParameterError, ref parameter, ref mapper, ref _behaviourContext); + currentVoiceState.UpdateParameters(out ErrorInfo updateParameterError, in parameter, mapper, ref _behaviourContext); if (updateParameterError.ErrorCode != ResultCode.Success) { _behaviourContext.AppendError(ref updateParameterError); } - currentVoiceState.UpdateWaveBuffers(out ErrorInfo[] waveBufferUpdateErrorInfos, ref parameter, voiceUpdateStates, ref mapper, ref _behaviourContext); + currentVoiceState.UpdateWaveBuffers(out ErrorInfo[] waveBufferUpdateErrorInfos, in parameter, voiceUpdateStates, mapper, ref _behaviourContext); foreach (ref ErrorInfo errorInfo in waveBufferUpdateErrorInfos.AsSpan()) { @@ -198,7 +193,7 @@ namespace Ryujinx.Audio.Renderer.Server } } - currentVoiceState.WriteOutStatus(ref outStatus, ref parameter, voiceUpdateStates); + currentVoiceState.WriteOutStatus(ref outStatus, in parameter, voiceUpdateStates); } } @@ -211,10 +206,12 @@ namespace Ryujinx.Audio.Renderer.Server Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.VoicesSize); + _inputReader.SetConsumed(initialInputConsumed + _inputHeader.VoicesSize); + return ResultCode.Success; } - private static void ResetEffect(ref BaseEffect effect, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + private static void ResetEffect(ref BaseEffect effect, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { effect.ForceUnmapBuffers(mapper); @@ -234,17 +231,17 @@ namespace Ryujinx.Audio.Renderer.Server }; } - public ResultCode UpdateEffects(EffectContext context, bool isAudioRendererActive, Memory memoryPools) + public ResultCode UpdateEffects(EffectContext context, bool isAudioRendererActive, PoolMapper mapper) { if (_behaviourContext.IsEffectInfoVersion2Supported()) { - return UpdateEffectsVersion2(context, isAudioRendererActive, memoryPools); + return UpdateEffectsVersion2(context, isAudioRendererActive, mapper); } - return UpdateEffectsVersion1(context, isAudioRendererActive, memoryPools); + return UpdateEffectsVersion1(context, isAudioRendererActive, mapper); } - public ResultCode UpdateEffectsVersion2(EffectContext context, bool isAudioRendererActive, Memory memoryPools) + public ResultCode UpdateEffectsVersion2(EffectContext context, bool isAudioRendererActive, PoolMapper mapper) { if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.EffectsSize) { @@ -253,26 +250,22 @@ namespace Ryujinx.Audio.Renderer.Server int initialOutputSize = _output.Length; - ReadOnlySpan parameters = MemoryMarshal.Cast(_input[..(int)_inputHeader.EffectsSize].Span); - - _input = _input[(int)_inputHeader.EffectsSize..]; - - PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled()); + long initialInputConsumed = _inputReader.Consumed; for (int i = 0; i < context.GetCount(); i++) { - EffectInParameterVersion2 parameter = parameters[i]; + ref readonly EffectInParameterVersion2 parameter = ref _inputReader.GetRefOrRefToCopy(out _); ref EffectOutStatusVersion2 outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; ref BaseEffect effect = ref context.GetEffect(i); - if (!effect.IsTypeValid(ref parameter)) + if (!effect.IsTypeValid(in parameter)) { - ResetEffect(ref effect, ref parameter, mapper); + ResetEffect(ref effect, in parameter, mapper); } - effect.Update(out ErrorInfo updateErrorInfo, ref parameter, mapper); + effect.Update(out ErrorInfo updateErrorInfo, in parameter, mapper); if (updateErrorInfo.ErrorCode != ResultCode.Success) { @@ -297,10 +290,12 @@ namespace Ryujinx.Audio.Renderer.Server Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize); + _inputReader.SetConsumed(initialInputConsumed + _inputHeader.EffectsSize); + return ResultCode.Success; } - public ResultCode UpdateEffectsVersion1(EffectContext context, bool isAudioRendererActive, Memory memoryPools) + public ResultCode UpdateEffectsVersion1(EffectContext context, bool isAudioRendererActive, PoolMapper mapper) { if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.EffectsSize) { @@ -309,26 +304,22 @@ namespace Ryujinx.Audio.Renderer.Server int initialOutputSize = _output.Length; - ReadOnlySpan parameters = MemoryMarshal.Cast(_input[..(int)_inputHeader.EffectsSize].Span); - - _input = _input[(int)_inputHeader.EffectsSize..]; - - PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled()); + long initialInputConsumed = _inputReader.Consumed; for (int i = 0; i < context.GetCount(); i++) { - EffectInParameterVersion1 parameter = parameters[i]; + ref readonly EffectInParameterVersion1 parameter = ref _inputReader.GetRefOrRefToCopy(out _); ref EffectOutStatusVersion1 outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; ref BaseEffect effect = ref context.GetEffect(i); - if (!effect.IsTypeValid(ref parameter)) + if (!effect.IsTypeValid(in parameter)) { - ResetEffect(ref effect, ref parameter, mapper); + ResetEffect(ref effect, in parameter, mapper); } - effect.Update(out ErrorInfo updateErrorInfo, ref parameter, mapper); + effect.Update(out ErrorInfo updateErrorInfo, in parameter, mapper); if (updateErrorInfo.ErrorCode != ResultCode.Success) { @@ -345,38 +336,40 @@ namespace Ryujinx.Audio.Renderer.Server Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize); + _inputReader.SetConsumed(initialInputConsumed + _inputHeader.EffectsSize); + return ResultCode.Success; } public ResultCode UpdateSplitter(SplitterContext context) { - if (context.Update(_input.Span, out int consumedSize)) + if (context.Update(ref _inputReader)) { - _input = _input[consumedSize..]; - return ResultCode.Success; } return ResultCode.InvalidUpdateInfo; } - private static bool CheckMixParametersValidity(MixContext mixContext, uint mixBufferCount, uint inputMixCount, ReadOnlySpan parameters) + private static bool CheckMixParametersValidity(MixContext mixContext, uint mixBufferCount, uint inputMixCount, SequenceReader parameters) { uint maxMixStateCount = mixContext.GetCount(); uint totalRequiredMixBufferCount = 0; for (int i = 0; i < inputMixCount; i++) { - if (parameters[i].IsUsed) + ref readonly MixParameter parameter = ref parameters.GetRefOrRefToCopy(out _); + + if (parameter.IsUsed) { - if (parameters[i].DestinationMixId != Constants.UnusedMixId && - parameters[i].DestinationMixId > maxMixStateCount && - parameters[i].MixId != Constants.FinalMixId) + if (parameter.DestinationMixId != Constants.UnusedMixId && + parameter.DestinationMixId > maxMixStateCount && + parameter.MixId != Constants.FinalMixId) { return true; } - totalRequiredMixBufferCount += parameters[i].BufferCount; + totalRequiredMixBufferCount += parameter.BufferCount; } } @@ -391,7 +384,7 @@ namespace Ryujinx.Audio.Renderer.Server if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()) { - MixInParameterDirtyOnlyUpdate parameter = MemoryMarshal.Cast(_input.Span)[0]; + ref readonly MixInParameterDirtyOnlyUpdate parameter = ref _inputReader.GetRefOrRefToCopy(out _); mixCount = parameter.MixCount; @@ -411,25 +404,20 @@ namespace Ryujinx.Audio.Renderer.Server return ResultCode.InvalidUpdateInfo; } - if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()) - { - _input = _input[Unsafe.SizeOf()..]; - } + long initialInputConsumed = _inputReader.Consumed; - ReadOnlySpan parameters = MemoryMarshal.Cast(_input.Span[..(int)inputMixSize]); + int parameterCount = (int)inputMixSize / Unsafe.SizeOf(); - _input = _input[(int)inputMixSize..]; - - if (CheckMixParametersValidity(mixContext, mixBufferCount, mixCount, parameters)) + if (CheckMixParametersValidity(mixContext, mixBufferCount, mixCount, _inputReader)) { return ResultCode.InvalidUpdateInfo; } bool isMixContextDirty = false; - for (int i = 0; i < parameters.Length; i++) + for (int i = 0; i < parameterCount; i++) { - MixParameter parameter = parameters[i]; + ref readonly MixParameter parameter = ref _inputReader.GetRefOrRefToCopy(out _); int mixId = i; @@ -454,7 +442,7 @@ namespace Ryujinx.Audio.Renderer.Server if (mix.IsUsed) { - isMixContextDirty |= mix.Update(mixContext.EdgeMatrix, ref parameter, effectContext, splitterContext, _behaviourContext); + isMixContextDirty |= mix.Update(mixContext.EdgeMatrix, in parameter, effectContext, splitterContext, _behaviourContext); } } @@ -473,10 +461,12 @@ namespace Ryujinx.Audio.Renderer.Server } } + _inputReader.SetConsumed(initialInputConsumed + inputMixSize); + return ResultCode.Success; } - private static void ResetSink(ref BaseSink sink, ref SinkInParameter parameter) + private static void ResetSink(ref BaseSink sink, in SinkInParameter parameter) { sink.CleanUp(); @@ -489,10 +479,8 @@ namespace Ryujinx.Audio.Renderer.Server }; } - public ResultCode UpdateSinks(SinkContext context, Memory memoryPools) + public ResultCode UpdateSinks(SinkContext context, PoolMapper mapper) { - PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled()); - if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.SinksSize) { return ResultCode.InvalidUpdateInfo; @@ -500,22 +488,20 @@ namespace Ryujinx.Audio.Renderer.Server int initialOutputSize = _output.Length; - ReadOnlySpan parameters = MemoryMarshal.Cast(_input[..(int)_inputHeader.SinksSize].Span); - - _input = _input[(int)_inputHeader.SinksSize..]; + long initialInputConsumed = _inputReader.Consumed; for (int i = 0; i < context.GetCount(); i++) { - SinkInParameter parameter = parameters[i]; + ref readonly SinkInParameter parameter = ref _inputReader.GetRefOrRefToCopy(out _); ref SinkOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; ref BaseSink sink = ref context.GetSink(i); - if (!sink.IsTypeValid(ref parameter)) + if (!sink.IsTypeValid(in parameter)) { - ResetSink(ref sink, ref parameter); + ResetSink(ref sink, in parameter); } - sink.Update(out ErrorInfo updateErrorInfo, ref parameter, ref outStatus, mapper); + sink.Update(out ErrorInfo updateErrorInfo, in parameter, ref outStatus, mapper); if (updateErrorInfo.ErrorCode != ResultCode.Success) { @@ -530,6 +516,8 @@ namespace Ryujinx.Audio.Renderer.Server Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.SinksSize); + _inputReader.SetConsumed(initialInputConsumed + _inputHeader.SinksSize); + return ResultCode.Success; } @@ -540,7 +528,7 @@ namespace Ryujinx.Audio.Renderer.Server return ResultCode.InvalidUpdateInfo; } - PerformanceInParameter parameter = SpanIOHelper.Read(ref _input); + ref readonly PerformanceInParameter parameter = ref _inputReader.GetRefOrRefToCopy(out _); ref PerformanceOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; @@ -585,9 +573,9 @@ namespace Ryujinx.Audio.Renderer.Server return ResultCode.Success; } - public ResultCode CheckConsumedSize() + public readonly ResultCode CheckConsumedSize() { - int consumedInputSize = _inputOrigin.Length - _input.Length; + long consumedInputSize = _inputReader.Consumed; int consumedOutputSize = _outputOrigin.Length - _output.Length; if (consumedInputSize != _inputHeader.TotalSize) diff --git a/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs b/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs index 225f7d31b0..040c70e6ce 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs @@ -254,7 +254,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice /// /// The user parameter. /// Return true, if the server voice information needs to be updated. - private readonly bool ShouldUpdateParameters(ref VoiceInParameter parameter) + private readonly bool ShouldUpdateParameters(in VoiceInParameter parameter) { if (DataSourceStateAddressInfo.CpuAddress == parameter.DataSourceStateAddress) { @@ -273,7 +273,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice /// The user parameter. /// The mapper to use. /// The behaviour context. - public void UpdateParameters(out ErrorInfo outErrorInfo, ref VoiceInParameter parameter, ref PoolMapper poolMapper, ref BehaviourContext behaviourContext) + public void UpdateParameters(out ErrorInfo outErrorInfo, in VoiceInParameter parameter, PoolMapper poolMapper, ref BehaviourContext behaviourContext) { InUse = parameter.InUse; Id = parameter.Id; @@ -326,7 +326,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice VoiceDropFlag = false; } - if (ShouldUpdateParameters(ref parameter)) + if (ShouldUpdateParameters(in parameter)) { DataSourceStateUnmapped = !poolMapper.TryAttachBuffer(out outErrorInfo, ref DataSourceStateAddressInfo, parameter.DataSourceStateAddress, parameter.DataSourceStateSize); } @@ -380,7 +380,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice /// The given user output. /// The user parameter. /// The voice states associated to the . - public void WriteOutStatus(ref VoiceOutStatus outStatus, ref VoiceInParameter parameter, ReadOnlySpan> voiceUpdateStates) + public void WriteOutStatus(ref VoiceOutStatus outStatus, in VoiceInParameter parameter, ReadOnlySpan> voiceUpdateStates) { #if DEBUG // Sanity check in debug mode of the internal state @@ -426,7 +426,12 @@ namespace Ryujinx.Audio.Renderer.Server.Voice /// The voice states associated to the . /// The mapper to use. /// The behaviour context. - public void UpdateWaveBuffers(out ErrorInfo[] errorInfos, ref VoiceInParameter parameter, ReadOnlySpan> voiceUpdateStates, ref PoolMapper mapper, ref BehaviourContext behaviourContext) + public void UpdateWaveBuffers( + out ErrorInfo[] errorInfos, + in VoiceInParameter parameter, + ReadOnlySpan> voiceUpdateStates, + PoolMapper mapper, + ref BehaviourContext behaviourContext) { errorInfos = new ErrorInfo[Constants.VoiceWaveBufferCount * 2]; @@ -444,7 +449,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice for (int i = 0; i < Constants.VoiceWaveBufferCount; i++) { - UpdateWaveBuffer(errorInfos.AsSpan(i * 2, 2), ref WaveBuffers[i], ref parameter.WaveBuffers[i], parameter.SampleFormat, voiceUpdateState.IsWaveBufferValid[i], ref mapper, ref behaviourContext); + UpdateWaveBuffer(errorInfos.AsSpan(i * 2, 2), ref WaveBuffers[i], ref parameter.WaveBuffers[i], parameter.SampleFormat, voiceUpdateState.IsWaveBufferValid[i], mapper, ref behaviourContext); } } @@ -458,7 +463,14 @@ namespace Ryujinx.Audio.Renderer.Server.Voice /// If set to true, the server side wavebuffer is considered valid. /// The mapper to use. /// The behaviour context. - private void UpdateWaveBuffer(Span errorInfos, ref WaveBuffer waveBuffer, ref WaveBufferInternal inputWaveBuffer, SampleFormat sampleFormat, bool isValid, ref PoolMapper mapper, ref BehaviourContext behaviourContext) + private void UpdateWaveBuffer( + Span errorInfos, + ref WaveBuffer waveBuffer, + ref WaveBufferInternal inputWaveBuffer, + SampleFormat sampleFormat, + bool isValid, + PoolMapper mapper, + ref BehaviourContext behaviourContext) { if (!isValid && waveBuffer.IsSendToAudioProcessor && waveBuffer.BufferAddressInfo.CpuAddress != 0) { diff --git a/src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs b/src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs new file mode 100644 index 0000000000..5403c87c07 --- /dev/null +++ b/src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs @@ -0,0 +1,181 @@ +using System; +using System.Buffers; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Common.Extensions +{ + public static class SequenceReaderExtensions + { + /// + /// Dumps the entire to a file, restoring its previous location afterward. + /// Useful for debugging purposes. + /// + /// The to write to a file + /// The path and name of the file to create and dump to + public static void DumpToFile(this ref SequenceReader reader, string fileFullName) + { + var initialConsumed = reader.Consumed; + + reader.Rewind(initialConsumed); + + using (var fileStream = System.IO.File.Create(fileFullName, 4096, System.IO.FileOptions.None)) + { + while (reader.End == false) + { + var span = reader.CurrentSpan; + fileStream.Write(span); + reader.Advance(span.Length); + } + } + + reader.SetConsumed(initialConsumed); + } + + /// + /// Returns a reference to the desired value. This ref should always be used. The argument passed in should never be used, as this is only used for storage if the value + /// must be copied from multiple segments held by the . + /// + /// Type to get + /// The to read from + /// A location used as storage if (and only if) the value to be read spans multiple segments + /// A reference to the desired value, either directly to memory in the , or to if it has been used for copying the value in to + /// + /// DO NOT use after calling this method, as it will only + /// contain a value if the value couldn't be referenced directly because it spans multiple segments. + /// To discourage use, it is recommended to to call this method like the following: + /// + /// ref readonly MyStruct value = ref sequenceReader.GetRefOrRefToCopy{MyStruct}(out _); + /// + /// + /// The does not contain enough data to read a value of type + public static ref readonly T GetRefOrRefToCopy(this scoped ref SequenceReader reader, out T copyDestinationIfRequiredDoNotUse) where T : unmanaged + { + int lengthRequired = Unsafe.SizeOf(); + + ReadOnlySpan span = reader.UnreadSpan; + if (lengthRequired <= span.Length) + { + reader.Advance(lengthRequired); + + copyDestinationIfRequiredDoNotUse = default; + + ReadOnlySpan spanOfT = MemoryMarshal.Cast(span); + + return ref spanOfT[0]; + } + else + { + copyDestinationIfRequiredDoNotUse = default; + + Span valueSpan = MemoryMarshal.CreateSpan(ref copyDestinationIfRequiredDoNotUse, 1); + + Span valueBytesSpan = MemoryMarshal.AsBytes(valueSpan); + + if (!reader.TryCopyTo(valueBytesSpan)) + { + throw new ArgumentOutOfRangeException(nameof(reader), "The sequence is not long enough to read the desired value."); + } + + reader.Advance(lengthRequired); + + return ref valueSpan[0]; + } + } + + /// + /// Reads an as little endian. + /// + /// The to read from + /// A location to receive the read value + /// Thrown if there wasn't enough data for an + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadLittleEndian(this ref SequenceReader reader, out int value) + { + if (!reader.TryReadLittleEndian(out value)) + { + throw new ArgumentOutOfRangeException(nameof(value), "The sequence is not long enough to read the desired value."); + } + } + + /// + /// Reads the desired unmanaged value by copying it to the specified . + /// + /// Type to read + /// The to read from + /// The target that will receive the read value + /// The does not contain enough data to read a value of type + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadUnmanaged(this ref SequenceReader reader, out T value) where T : unmanaged + { + if (!reader.TryReadUnmanaged(out value)) + { + throw new ArgumentOutOfRangeException(nameof(value), "The sequence is not long enough to read the desired value."); + } + } + + /// + /// Sets the reader's position as bytes consumed. + /// + /// The to set the position + /// The number of bytes consumed + public static void SetConsumed(ref this SequenceReader reader, long consumed) + { + reader.Rewind(reader.Consumed); + reader.Advance(consumed); + } + + /// + /// Try to read the given type out of the buffer if possible. Warning: this is dangerous to use with arbitrary + /// structs - see remarks for full details. + /// + /// Type to read + /// + /// IMPORTANT: The read is a straight copy of bits. If a struct depends on specific state of it's members to + /// behave correctly this can lead to exceptions, etc. If reading endian specific integers, use the explicit + /// overloads such as + /// + /// + /// True if successful. will be default if failed (due to lack of space). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe bool TryReadUnmanaged(ref this SequenceReader reader, out T value) where T : unmanaged + { + ReadOnlySpan span = reader.UnreadSpan; + + if (span.Length < sizeof(T)) + { + return TryReadUnmanagedMultiSegment(ref reader, out value); + } + + value = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(span)); + + reader.Advance(sizeof(T)); + + return true; + } + + private static unsafe bool TryReadUnmanagedMultiSegment(ref SequenceReader reader, out T value) where T : unmanaged + { + Debug.Assert(reader.UnreadSpan.Length < sizeof(T)); + + // Not enough data in the current segment, try to peek for the data we need. + T buffer = default; + + Span tempSpan = new Span(&buffer, sizeof(T)); + + if (!reader.TryCopyTo(tempSpan)) + { + value = default; + return false; + } + + value = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(tempSpan)); + + reader.Advance(sizeof(T)); + + return true; + } + } +} diff --git a/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs b/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs index 6dffcd295b..0acb57be42 100644 --- a/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs +++ b/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs @@ -1,10 +1,12 @@ using ARMeilleure.Memory; +using Ryujinx.Common.Memory; using Ryujinx.Cpu.Jit.HostTracked; using Ryujinx.Cpu.Signal; using Ryujinx.Memory; using Ryujinx.Memory.Range; using Ryujinx.Memory.Tracking; using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; @@ -237,11 +239,11 @@ namespace Ryujinx.Cpu.Jit } else { - Memory memory = new byte[size]; + IMemoryOwner memoryOwner = ByteMemoryPool.Rent(size); - Read(va, memory.Span); + Read(va, memoryOwner.Memory.Span); - return new WritableRegion(this, va, memory); + return new WritableRegion(this, va, memoryOwner); } } diff --git a/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs b/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs index 4c8d441b63..d1be8298d6 100644 --- a/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs +++ b/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs @@ -17,6 +17,8 @@ namespace Ryujinx.Horizon.Generators.Hipc private const string ResponseVariableName = "response"; private const string OutRawDataVariableName = "outRawData"; + private const string TypeSystemBuffersReadOnlySequence = "System.Buffers.ReadOnlySequence"; + private const string TypeSystemMemory = "System.Memory"; private const string TypeSystemReadOnlySpan = "System.ReadOnlySpan"; private const string TypeSystemSpan = "System.Span"; private const string TypeStructLayoutAttribute = "System.Runtime.InteropServices.StructLayoutAttribute"; @@ -329,7 +331,15 @@ namespace Ryujinx.Horizon.Generators.Hipc value = $"{InObjectsVariableName}[{inObjectIndex++}]"; break; case CommandArgType.Buffer: - if (IsReadOnlySpan(compilation, parameter)) + if (IsMemory(compilation, parameter)) + { + value = $"CommandSerialization.GetWritableRegion(processor.GetBufferRange({index}))"; + } + else if (IsReadOnlySequence(compilation, parameter)) + { + value = $"CommandSerialization.GetReadOnlySequence(processor.GetBufferRange({index}))"; + } + else if (IsReadOnlySpan(compilation, parameter)) { string spanGenericTypeName = GetCanonicalTypeNameOfGenericArgument(compilation, parameter.Type, 0); value = GenerateSpanCast(spanGenericTypeName, $"CommandSerialization.GetReadOnlySpan(processor.GetBufferRange({index}))"); @@ -346,7 +356,13 @@ namespace Ryujinx.Horizon.Generators.Hipc break; } - if (IsSpan(compilation, parameter)) + if (IsMemory(compilation, parameter)) + { + generator.AppendLine($"using var {argName} = {value};"); + + argName = $"{argName}.Memory"; + } + else if (IsSpan(compilation, parameter)) { generator.AppendLine($"using var {argName} = {value};"); @@ -637,7 +653,9 @@ namespace Ryujinx.Horizon.Generators.Hipc private static bool IsValidTypeForBuffer(Compilation compilation, ParameterSyntax parameter) { - return IsReadOnlySpan(compilation, parameter) || + return IsMemory(compilation, parameter) || + IsReadOnlySequence(compilation, parameter) || + IsReadOnlySpan(compilation, parameter) || IsSpan(compilation, parameter) || IsUnmanagedType(compilation, parameter.Type); } @@ -649,6 +667,16 @@ namespace Ryujinx.Horizon.Generators.Hipc return typeInfo.Type.IsUnmanagedType; } + private static bool IsMemory(Compilation compilation, ParameterSyntax parameter) + { + return GetCanonicalTypeName(compilation, parameter.Type) == TypeSystemMemory; + } + + private static bool IsReadOnlySequence(Compilation compilation, ParameterSyntax parameter) + { + return GetCanonicalTypeName(compilation, parameter.Type) == TypeSystemBuffersReadOnlySequence; + } + private static bool IsReadOnlySpan(Compilation compilation, ParameterSyntax parameter) { return GetCanonicalTypeName(compilation, parameter.Type) == TypeSystemReadOnlySpan; diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs index 776df641ac..54de072105 100644 --- a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs @@ -57,23 +57,11 @@ namespace Ryujinx.Horizon.Sdk.Audio.Detail [CmifCommand(4)] public Result RequestUpdate( - [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span output, - [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span performanceOutput, - [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan input) + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Memory output, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Memory performanceOutput, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySequence input) { - using IMemoryOwner outputOwner = ByteMemoryPool.Rent(output.Length); - using IMemoryOwner performanceOutputOwner = ByteMemoryPool.Rent(performanceOutput.Length); - - Memory outputMemory = outputOwner.Memory; - Memory performanceOutputMemory = performanceOutputOwner.Memory; - - using MemoryHandle outputHandle = outputMemory.Pin(); - using MemoryHandle performanceOutputHandle = performanceOutputMemory.Pin(); - - Result result = new Result((int)_renderSystem.Update(outputMemory, performanceOutputMemory, input.ToArray())); - - outputMemory.Span.CopyTo(output); - performanceOutputMemory.Span.CopyTo(performanceOutput); + Result result = new Result((int)_renderSystem.Update(output, performanceOutput, input)); return result; } @@ -127,9 +115,9 @@ namespace Ryujinx.Horizon.Sdk.Audio.Detail [CmifCommand(10)] // 3.0.0+ public Result RequestUpdateAuto( - [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span output, - [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span performanceOutput, - [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan input) + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Memory output, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Memory performanceOutput, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySequence input) { return RequestUpdate(output, performanceOutput, input); } diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRenderer.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRenderer.cs index e4ca2e8ec4..b766bd73c3 100644 --- a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRenderer.cs +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRenderer.cs @@ -1,6 +1,7 @@ using Ryujinx.Horizon.Common; using Ryujinx.Horizon.Sdk.Sf; using System; +using System.Buffers; namespace Ryujinx.Horizon.Sdk.Audio.Detail { @@ -10,13 +11,13 @@ namespace Ryujinx.Horizon.Sdk.Audio.Detail Result GetSampleCount(out int sampleCount); Result GetMixBufferCount(out int mixBufferCount); Result GetState(out int state); - Result RequestUpdate(Span output, Span performanceOutput, ReadOnlySpan input); + Result RequestUpdate(Memory output, Memory performanceOutput, ReadOnlySequence input); Result Start(); Result Stop(); Result QuerySystemEvent(out int eventHandle); Result SetRenderingTimeLimit(int percent); Result GetRenderingTimeLimit(out int percent); - Result RequestUpdateAuto(Span output, Span performanceOutput, ReadOnlySpan input); + Result RequestUpdateAuto(Memory output, Memory performanceOutput, ReadOnlySequence input); Result ExecuteAudioRendererRendering(); Result SetVoiceDropParameter(float voiceDropParameter); Result GetVoiceDropParameter(out float voiceDropParameter); diff --git a/src/Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs b/src/Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs index 038135ac86..7f52846481 100644 --- a/src/Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs +++ b/src/Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs @@ -2,6 +2,7 @@ using Ryujinx.Horizon.Sdk.Sf.Cmif; using Ryujinx.Horizon.Sdk.Sf.Hipc; using Ryujinx.Memory; using System; +using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -9,6 +10,11 @@ namespace Ryujinx.Horizon.Sdk.Sf { static class CommandSerialization { + public static ReadOnlySequence GetReadOnlySequence(PointerAndSize bufferRange) + { + return HorizonStatic.AddressSpace.GetReadOnlySequence(bufferRange.Address, checked((int)bufferRange.Size)); + } + public static ReadOnlySpan GetReadOnlySpan(PointerAndSize bufferRange) { return HorizonStatic.AddressSpace.GetSpan(bufferRange.Address, checked((int)bufferRange.Size)); diff --git a/src/Ryujinx.Tests/Common/Extensions/SequenceReaderExtensionsTests.cs b/src/Ryujinx.Tests/Common/Extensions/SequenceReaderExtensionsTests.cs new file mode 100644 index 0000000000..c0127530af --- /dev/null +++ b/src/Ryujinx.Tests/Common/Extensions/SequenceReaderExtensionsTests.cs @@ -0,0 +1,359 @@ +using NUnit.Framework; +using Ryujinx.Common.Extensions; +using Ryujinx.Memory; +using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Tests.Common.Extensions +{ + public class SequenceReaderExtensionsTests + { + [TestCase(null)] + [TestCase(sizeof(int) + 1)] + public void GetRefOrRefToCopy_ReadsMultiSegmentedSequenceSuccessfully(int? maxSegmentSize) + { + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(3).ToArray(); + + ReadOnlySequence sequence = + CreateSegmentedByteSequence(originalStructs, maxSegmentSize ?? Unsafe.SizeOf()); + + var sequenceReader = new SequenceReader(sequence); + + foreach (var original in originalStructs) + { + // Act + ref readonly MyUnmanagedStruct read = ref sequenceReader.GetRefOrRefToCopy(out _); + + // Assert + MyUnmanagedStruct.Assert(Assert.AreEqual, original, read); + } + } + + [Test] + public void GetRefOrRefToCopy_FragmentedSequenceReturnsRefToCopy() + { + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(1).ToArray(); + + ReadOnlySequence sequence = CreateSegmentedByteSequence(originalStructs, 3); + + var sequenceReader = new SequenceReader(sequence); + + foreach (var original in originalStructs) + { + // Act + ref readonly MyUnmanagedStruct read = ref sequenceReader.GetRefOrRefToCopy(out var copy); + + // Assert + MyUnmanagedStruct.Assert(Assert.AreEqual, original, read); + MyUnmanagedStruct.Assert(Assert.AreEqual, read, copy); + } + } + + [Test] + public void GetRefOrRefToCopy_ContiguousSequenceReturnsRefToBuffer() + { + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(1).ToArray(); + + ReadOnlySequence sequence = CreateSegmentedByteSequence(originalStructs, int.MaxValue); + + var sequenceReader = new SequenceReader(sequence); + + foreach (var original in originalStructs) + { + // Act + ref readonly MyUnmanagedStruct read = ref sequenceReader.GetRefOrRefToCopy(out var copy); + + // Assert + MyUnmanagedStruct.Assert(Assert.AreEqual, original, read); + MyUnmanagedStruct.Assert(Assert.AreNotEqual, read, copy); + } + } + + [Test] + public void GetRefOrRefToCopy_ThrowsWhenNotEnoughData() + { + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(1).ToArray(); + + ReadOnlySequence sequence = CreateSegmentedByteSequence(originalStructs, int.MaxValue); + + // Act/Assert + Assert.Throws(() => + { + var sequenceReader = new SequenceReader(sequence); + + sequenceReader.Advance(1); + + ref readonly MyUnmanagedStruct result = ref sequenceReader.GetRefOrRefToCopy(out _); + }); + } + + [Test] + public void ReadLittleEndian_Int32_RoundTripsSuccessfully() + { + // Arrange + const int TestValue = 0x1234abcd; + + byte[] buffer = new byte[sizeof(int)]; + + BinaryPrimitives.WriteInt32LittleEndian(buffer.AsSpan(), TestValue); + + var sequenceReader = new SequenceReader(new ReadOnlySequence(buffer)); + + // Act + sequenceReader.ReadLittleEndian(out int roundTrippedValue); + + // Assert + Assert.AreEqual(TestValue, roundTrippedValue); + } + + [Test] + public void ReadLittleEndian_Int32_ResultIsNotBigEndian() + { + // Arrange + const int TestValue = 0x1234abcd; + + byte[] buffer = new byte[sizeof(int)]; + + BinaryPrimitives.WriteInt32BigEndian(buffer.AsSpan(), TestValue); + + var sequenceReader = new SequenceReader(new ReadOnlySequence(buffer)); + + // Act + sequenceReader.ReadLittleEndian(out int roundTrippedValue); + + // Assert + Assert.AreNotEqual(TestValue, roundTrippedValue); + } + + [Test] + public void ReadLittleEndian_Int32_ThrowsWhenNotEnoughData() + { + // Arrange + const int TestValue = 0x1234abcd; + + byte[] buffer = new byte[sizeof(int)]; + + BinaryPrimitives.WriteInt32BigEndian(buffer.AsSpan(), TestValue); + + // Act/Assert + Assert.Throws(() => + { + var sequenceReader = new SequenceReader(new ReadOnlySequence(buffer)); + sequenceReader.Advance(1); + + sequenceReader.ReadLittleEndian(out int roundTrippedValue); + }); + } + + [Test] + public void ReadUnmanaged_ContiguousSequence_Succeeds() + => ReadUnmanaged_Succeeds(int.MaxValue); + + [Test] + public void ReadUnmanaged_FragmentedSequence_Succeeds() + => ReadUnmanaged_Succeeds(sizeof(int) + 1); + + [Test] + public void ReadUnmanaged_ThrowsWhenNotEnoughData() + { + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(1).ToArray(); + + ReadOnlySequence sequence = CreateSegmentedByteSequence(originalStructs, int.MaxValue); + + // Act/Assert + Assert.Throws(() => + { + var sequenceReader = new SequenceReader(sequence); + + sequenceReader.Advance(1); + + sequenceReader.ReadUnmanaged(out MyUnmanagedStruct read); + }); + } + + [Test] + public void SetConsumed_ContiguousSequence_SucceedsWhenValid() + => SetConsumed_SucceedsWhenValid(int.MaxValue); + + [Test] + public void SetConsumed_FragmentedSequence_SucceedsWhenValid() + => SetConsumed_SucceedsWhenValid(sizeof(int) + 1); + + [Test] + public void SetConsumed_ThrowsWhenBeyondActualLength() + { + const int StructCount = 2; + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(StructCount).ToArray(); + + ReadOnlySequence sequence = CreateSegmentedByteSequence(originalStructs, MyUnmanagedStruct.SizeOf); + + Assert.Throws(() => + { + var sequenceReader = new SequenceReader(sequence); + + sequenceReader.SetConsumed(MyUnmanagedStruct.SizeOf * StructCount + 1); + }); + } + + private static void ReadUnmanaged_Succeeds(int maxSegmentLength) + { + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(3).ToArray(); + + ReadOnlySequence sequence = CreateSegmentedByteSequence(originalStructs, maxSegmentLength); + + var sequenceReader = new SequenceReader(sequence); + + foreach (var original in originalStructs) + { + // Act + sequenceReader.ReadUnmanaged(out MyUnmanagedStruct read); + + // Assert + MyUnmanagedStruct.Assert(Assert.AreEqual, original, read); + } + } + + private static void SetConsumed_SucceedsWhenValid(int maxSegmentLength) + { + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(2).ToArray(); + + ReadOnlySequence sequence = CreateSegmentedByteSequence(originalStructs, maxSegmentLength); + + var sequenceReader = new SequenceReader(sequence); + + static void SetConsumedAndAssert(scoped ref SequenceReader sequenceReader, long consumed) + { + sequenceReader.SetConsumed(consumed); + Assert.AreEqual(consumed, sequenceReader.Consumed); + } + + // Act/Assert + ref readonly MyUnmanagedStruct struct0A = ref sequenceReader.GetRefOrRefToCopy(out _); + + Assert.AreEqual(sequenceReader.Consumed, MyUnmanagedStruct.SizeOf); + + SetConsumedAndAssert(ref sequenceReader, 0); + + ref readonly MyUnmanagedStruct struct0B = ref sequenceReader.GetRefOrRefToCopy(out _); + + MyUnmanagedStruct.Assert(Assert.AreEqual, struct0A, struct0B); + + SetConsumedAndAssert(ref sequenceReader, 1); + + SetConsumedAndAssert(ref sequenceReader, MyUnmanagedStruct.SizeOf); + + ref readonly MyUnmanagedStruct struct1A = ref sequenceReader.GetRefOrRefToCopy(out _); + + SetConsumedAndAssert(ref sequenceReader, MyUnmanagedStruct.SizeOf); + + ref readonly MyUnmanagedStruct struct1B = ref sequenceReader.GetRefOrRefToCopy(out _); + + MyUnmanagedStruct.Assert(Assert.AreEqual, struct1A, struct1B); + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private struct MyUnmanagedStruct + { + public int BehaviourSize; + public int MemoryPoolsSize; + public short VoicesSize; + public int VoiceResourcesSize; + public short EffectsSize; + public int RenderInfoSize; + + public unsafe fixed byte Reserved[16]; + + public static readonly int SizeOf = Unsafe.SizeOf(); + + public static unsafe MyUnmanagedStruct Generate(Random rng) + { + const int BaseInt32Value = 0x1234abcd; + const short BaseInt16Value = 0x5678; + + var result = new MyUnmanagedStruct + { + BehaviourSize = BaseInt32Value ^ rng.Next(), + MemoryPoolsSize = BaseInt32Value ^ rng.Next(), + VoicesSize = (short)(BaseInt16Value ^ rng.Next()), + VoiceResourcesSize = BaseInt32Value ^ rng.Next(), + EffectsSize = (short)(BaseInt16Value ^ rng.Next()), + RenderInfoSize = BaseInt32Value ^ rng.Next(), + }; + + Unsafe.Write(result.Reserved, rng.NextInt64()); + + return result; + } + + public static unsafe void Assert(Action assert, in MyUnmanagedStruct expected, in MyUnmanagedStruct actual) + { + assert(expected.BehaviourSize, actual.BehaviourSize); + assert(expected.MemoryPoolsSize, actual.MemoryPoolsSize); + assert(expected.VoicesSize, actual.VoicesSize); + assert(expected.VoiceResourcesSize, actual.VoiceResourcesSize); + assert(expected.EffectsSize, actual.EffectsSize); + assert(expected.RenderInfoSize, actual.RenderInfoSize); + + fixed (void* expectedReservedPtr = expected.Reserved) + fixed (void* actualReservedPtr = actual.Reserved) + { + long expectedReservedLong = Unsafe.Read(expectedReservedPtr); + long actualReservedLong = Unsafe.Read(actualReservedPtr); + + assert(expectedReservedLong, actualReservedLong); + } + } + } + + private static IEnumerable EnumerateNewUnmanagedStructs() + { + var rng = new Random(0); + + while (true) + { + yield return MyUnmanagedStruct.Generate(rng); + } + } + + private static ReadOnlySequence CreateSegmentedByteSequence(T[] array, int maxSegmentLength) where T : unmanaged + { + byte[] arrayBytes = MemoryMarshal.AsBytes(array.AsSpan()).ToArray(); + var memory = new Memory(arrayBytes); + int index = 0; + + BytesReadOnlySequenceSegment first = null, last = null; + + while (index < memory.Length) + { + int nextSegmentLength = Math.Min(maxSegmentLength, memory.Length - index); + var nextSegment = memory.Slice(index, nextSegmentLength); + + if (first == null) + { + first = last = new BytesReadOnlySequenceSegment(nextSegment); + } + else + { + last = last.Append(nextSegment); + } + + index += nextSegmentLength; + } + + return new ReadOnlySequence(first, 0, last, (int)(memory.Length - last.RunningIndex)); + } + } +} From 808803d97a0c06809bf000687c252f960048fcf0 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Sun, 7 Apr 2024 18:17:49 -0300 Subject: [PATCH 38/87] CPU: Fix PC alignment for ADR thumb instruction (#6613) * Fix PC alignment for ADR thumb instruction * PPTC version bump --- src/ARMeilleure/Instructions/InstEmitAlu32.cs | 12 ++++++++++++ src/ARMeilleure/Translation/PTC/Ptc.cs | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/ARMeilleure/Instructions/InstEmitAlu32.cs b/src/ARMeilleure/Instructions/InstEmitAlu32.cs index 3a5e71bccf..028ffbeb13 100644 --- a/src/ARMeilleure/Instructions/InstEmitAlu32.cs +++ b/src/ARMeilleure/Instructions/InstEmitAlu32.cs @@ -19,6 +19,12 @@ namespace ARMeilleure.Instructions Operand n = GetAluN(context); Operand m = GetAluM(context, setCarry: false); + if (op.Rn == RegisterAlias.Aarch32Pc && op is OpCodeT32AluImm12) + { + // For ADR, PC is always 4 bytes aligned, even in Thumb mode. + n = context.BitwiseAnd(n, Const(~3u)); + } + Operand res = context.Add(n, m); if (ShouldSetFlags(context)) @@ -467,6 +473,12 @@ namespace ARMeilleure.Instructions Operand n = GetAluN(context); Operand m = GetAluM(context, setCarry: false); + if (op.Rn == RegisterAlias.Aarch32Pc && op is OpCodeT32AluImm12) + { + // For ADR, PC is always 4 bytes aligned, even in Thumb mode. + n = context.BitwiseAnd(n, Const(~3u)); + } + Operand res = context.Subtract(n, m); if (ShouldSetFlags(context)) diff --git a/src/ARMeilleure/Translation/PTC/Ptc.cs b/src/ARMeilleure/Translation/PTC/Ptc.cs index 6f6dfcadf3..f987284fa1 100644 --- a/src/ARMeilleure/Translation/PTC/Ptc.cs +++ b/src/ARMeilleure/Translation/PTC/Ptc.cs @@ -29,7 +29,7 @@ namespace ARMeilleure.Translation.PTC private const string OuterHeaderMagicString = "PTCohd\0\0"; private const string InnerHeaderMagicString = "PTCihd\0\0"; - private const uint InternalVersion = 5518; //! To be incremented manually for each change to the ARMeilleure project. + private const uint InternalVersion = 6613; //! To be incremented manually for each change to the ARMeilleure project. private const string ActualDir = "0"; private const string BackupDir = "1"; From 3e6e0e4afaa3c3ffb118cb17b61feb16966a7eeb Mon Sep 17 00:00:00 2001 From: gdkchan Date: Sun, 7 Apr 2024 18:25:55 -0300 Subject: [PATCH 39/87] Add support for large sampler arrays on Vulkan (#6489) * Add support for large sampler arrays on Vulkan * Shader cache version bump * Format whitespace * Move DescriptorSetManager to PipelineLayoutCacheEntry to allow different pool sizes per layout * Handle array textures with different types on the same buffer * Somewhat better caching system * Avoid useless buffer data modification checks * Move redundant bindings update checking to the backend * Fix an issue where texture arrays would get the same bindings across stages on Vulkan * Backport some fixes from part 2 * Fix typo * PR feedback * Format whitespace * Add some missing XML docs --- src/Ryujinx.Graphics.GAL/IImageArray.cs | 8 + src/Ryujinx.Graphics.GAL/IPipeline.cs | 2 + src/Ryujinx.Graphics.GAL/IRenderer.cs | 4 + src/Ryujinx.Graphics.GAL/ITextureArray.cs | 8 + .../Multithreading/CommandHelper.cs | 12 + .../Multithreading/CommandType.cs | 10 + .../ImageArray/ImageArraySetFormatsCommand.cs | 26 + .../ImageArray/ImageArraySetImagesCommand.cs | 27 + .../Renderer/CreateImageArrayCommand.cs | 25 + .../Renderer/CreateTextureArrayCommand.cs | 25 + .../Commands/SetImageArrayCommand.cs | 26 + .../Commands/SetTextureArrayCommand.cs | 26 + .../TextureArraySetSamplersCommand.cs | 27 + .../TextureArraySetTexturesCommand.cs | 27 + .../Resources/ThreadedImageArray.cs | 36 + .../Resources/ThreadedTextureArray.cs | 37 + .../Multithreading/ThreadedPipeline.cs | 12 + .../Multithreading/ThreadedRenderer.cs | 17 + src/Ryujinx.Graphics.GAL/ResourceLayout.cs | 8 +- src/Ryujinx.Graphics.Gpu/Constants.cs | 5 + .../Engine/ShaderTexture.cs | 2 +- .../Image/AutoDeleteCache.cs | 3 +- src/Ryujinx.Graphics.Gpu/Image/Pool.cs | 32 + .../Image/TextureBindingInfo.cs | 12 +- .../Image/TextureBindingsArrayCache.cs | 714 ++++++++++++++++++ .../Image/TextureBindingsManager.cs | 54 +- .../Memory/BufferBounds.cs | 23 +- .../Memory/BufferManager.cs | 106 ++- .../Memory/BufferTextureArrayBinding.cs | 66 ++ .../Shader/CachedShaderBindings.cs | 17 +- .../Shader/DiskCache/DiskCacheGpuAccessor.cs | 29 +- .../Shader/DiskCache/DiskCacheHostStorage.cs | 2 +- .../Shader/DiskCache/DiskCacheLoadResult.cs | 6 + .../Shader/GpuAccessor.cs | 29 +- .../Shader/GpuAccessorBase.cs | 93 ++- .../Shader/ShaderInfoBuilder.cs | 35 +- .../Shader/ShaderSpecializationState.cs | 75 ++ .../Image/ImageArray.cs | 67 ++ .../Image/TextureArray.cs | 52 ++ src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs | 10 + src/Ryujinx.Graphics.OpenGL/Pipeline.cs | 9 + .../CodeGen/Glsl/Declarations.cs | 46 +- .../CodeGen/Glsl/Instructions/InstGen.cs | 10 +- .../Glsl/Instructions/InstGenBallot.cs | 2 +- .../CodeGen/Glsl/Instructions/InstGenCall.cs | 2 +- .../Glsl/Instructions/InstGenHelper.cs | 2 +- .../Glsl/Instructions/InstGenMemory.cs | 173 +---- .../Glsl/Instructions/InstGenPacking.cs | 12 +- .../Glsl/Instructions/InstGenShuffle.cs | 4 +- .../Glsl/Instructions/InstGenVector.cs | 4 +- .../CodeGen/Glsl/OperandManager.cs | 4 +- .../CodeGen/Spirv/CodeGenContext.cs | 4 +- .../CodeGen/Spirv/Declarations.cs | 39 +- .../CodeGen/Spirv/ImageDeclaration.cs | 20 + .../CodeGen/Spirv/Instructions.cs | 224 ++---- .../CodeGen/Spirv/SamplerDeclaration.cs | 27 + src/Ryujinx.Graphics.Shader/IGpuAccessor.cs | 43 +- .../IntermediateRepresentation/Instruction.cs | 12 + .../IntermediateRepresentation/Operation.cs | 10 +- .../TextureOperation.cs | 3 +- src/Ryujinx.Graphics.Shader/SamplerType.cs | 35 +- .../StructuredIr/TextureDefinition.cs | 6 +- .../TextureDescriptor.cs | 11 +- src/Ryujinx.Graphics.Shader/TextureHandle.cs | 4 +- .../Optimizations/BindlessElimination.cs | 322 ++++---- .../Optimizations/BindlessToArray.cs | 236 ++++++ .../Optimizations/BindlessToIndexed.cs | 118 --- .../Translation/Optimizations/Optimizer.cs | 10 +- .../Translation/ResourceManager.cs | 208 ++--- .../Translation/TransformContext.cs | 3 + .../Translation/Transforms/TexturePass.cs | 43 +- .../Translation/TranslatorContext.cs | 7 +- .../DescriptorSetManager.cs | 2 +- .../DescriptorSetUpdater.cs | 209 ++++- src/Ryujinx.Graphics.Vulkan/ImageArray.cs | 179 +++++ src/Ryujinx.Graphics.Vulkan/PipelineBase.cs | 20 + .../PipelineLayoutCacheEntry.cs | 76 +- .../ResourceBindingSegment.cs | 4 +- .../ResourceLayoutBuilder.cs | 2 +- .../ShaderCollection.cs | 30 +- src/Ryujinx.Graphics.Vulkan/TextureArray.cs | 194 +++++ src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs | 14 +- src/Ryujinx.ShaderTools/Program.cs | 40 + 83 files changed, 3263 insertions(+), 955 deletions(-) create mode 100644 src/Ryujinx.Graphics.GAL/IImageArray.cs create mode 100644 src/Ryujinx.Graphics.GAL/ITextureArray.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArraySetFormatsCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArraySetImagesCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateImageArrayCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateTextureArrayCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageArrayCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureArrayCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureAndSamplerArray/TextureArraySetSamplersCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureAndSamplerArray/TextureArraySetTexturesCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedImageArray.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTextureArray.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/BufferTextureArrayBinding.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Image/ImageArray.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Image/TextureArray.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Spirv/ImageDeclaration.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SamplerDeclaration.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs delete mode 100644 src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToIndexed.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/ImageArray.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/TextureArray.cs diff --git a/src/Ryujinx.Graphics.GAL/IImageArray.cs b/src/Ryujinx.Graphics.GAL/IImageArray.cs new file mode 100644 index 0000000000..30cff50b15 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/IImageArray.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.GAL +{ + public interface IImageArray + { + void SetFormats(int index, Format[] imageFormats); + void SetImages(int index, ITexture[] images); + } +} diff --git a/src/Ryujinx.Graphics.GAL/IPipeline.cs b/src/Ryujinx.Graphics.GAL/IPipeline.cs index 3ba084aa5b..9efb9e3e8f 100644 --- a/src/Ryujinx.Graphics.GAL/IPipeline.cs +++ b/src/Ryujinx.Graphics.GAL/IPipeline.cs @@ -59,6 +59,7 @@ namespace Ryujinx.Graphics.GAL void SetIndexBuffer(BufferRange buffer, IndexType type); void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat); + void SetImageArray(ShaderStage stage, int binding, IImageArray array); void SetLineParameters(float width, bool smooth); @@ -89,6 +90,7 @@ namespace Ryujinx.Graphics.GAL void SetStorageBuffers(ReadOnlySpan buffers); void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler); + void SetTextureArray(ShaderStage stage, int binding, ITextureArray array); void SetTransformFeedbackBuffers(ReadOnlySpan buffers); void SetUniformBuffers(ReadOnlySpan buffers); diff --git a/src/Ryujinx.Graphics.GAL/IRenderer.cs b/src/Ryujinx.Graphics.GAL/IRenderer.cs index 3bf56465eb..a3466e3966 100644 --- a/src/Ryujinx.Graphics.GAL/IRenderer.cs +++ b/src/Ryujinx.Graphics.GAL/IRenderer.cs @@ -21,10 +21,14 @@ namespace Ryujinx.Graphics.GAL BufferHandle CreateBuffer(nint pointer, int size); BufferHandle CreateBufferSparse(ReadOnlySpan storageBuffers); + IImageArray CreateImageArray(int size, bool isBuffer); + IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info); ISampler CreateSampler(SamplerCreateInfo info); ITexture CreateTexture(TextureCreateInfo info); + ITextureArray CreateTextureArray(int size, bool isBuffer); + bool PrepareHostMapping(nint address, ulong size); void CreateSync(ulong id, bool strict); diff --git a/src/Ryujinx.Graphics.GAL/ITextureArray.cs b/src/Ryujinx.Graphics.GAL/ITextureArray.cs new file mode 100644 index 0000000000..35c2116b54 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/ITextureArray.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.GAL +{ + public interface ITextureArray + { + void SetSamplers(int index, ISampler[] samplers); + void SetTextures(int index, ITexture[] textures); + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs b/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs index 5bf3d32835..fd2919be4d 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs @@ -1,10 +1,12 @@ using Ryujinx.Graphics.GAL.Multithreading.Commands; using Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer; using Ryujinx.Graphics.GAL.Multithreading.Commands.CounterEvent; +using Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray; using Ryujinx.Graphics.GAL.Multithreading.Commands.Program; using Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer; using Ryujinx.Graphics.GAL.Multithreading.Commands.Sampler; using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture; +using Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray; using Ryujinx.Graphics.GAL.Multithreading.Commands.Window; using System; using System.Linq; @@ -46,10 +48,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading Register(CommandType.CreateBufferAccess); Register(CommandType.CreateBufferSparse); Register(CommandType.CreateHostBuffer); + Register(CommandType.CreateImageArray); Register(CommandType.CreateProgram); Register(CommandType.CreateSampler); Register(CommandType.CreateSync); Register(CommandType.CreateTexture); + Register(CommandType.CreateTextureArray); Register(CommandType.GetCapabilities); Register(CommandType.PreFrame); Register(CommandType.ReportCounter); @@ -63,6 +67,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading Register(CommandType.CounterEventDispose); Register(CommandType.CounterEventFlush); + Register(CommandType.ImageArraySetFormats); + Register(CommandType.ImageArraySetImages); + Register(CommandType.ProgramDispose); Register(CommandType.ProgramGetBinary); Register(CommandType.ProgramCheckLink); @@ -82,6 +89,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading Register(CommandType.TextureSetDataSliceRegion); Register(CommandType.TextureSetStorage); + Register(CommandType.TextureArraySetSamplers); + Register(CommandType.TextureArraySetTextures); + Register(CommandType.WindowPresent); Register(CommandType.Barrier); @@ -114,6 +124,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading Register(CommandType.SetTransformFeedbackBuffers); Register(CommandType.SetUniformBuffers); Register(CommandType.SetImage); + Register(CommandType.SetImageArray); Register(CommandType.SetIndexBuffer); Register(CommandType.SetLineParameters); Register(CommandType.SetLogicOpState); @@ -130,6 +141,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading Register(CommandType.SetScissor); Register(CommandType.SetStencilTest); Register(CommandType.SetTextureAndSampler); + Register(CommandType.SetTextureArray); Register(CommandType.SetUserClipDistance); Register(CommandType.SetVertexAttribs); Register(CommandType.SetVertexBuffers); diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs b/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs index 6be639253a..a5e7336cdf 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs @@ -7,10 +7,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading CreateBufferAccess, CreateBufferSparse, CreateHostBuffer, + CreateImageArray, CreateProgram, CreateSampler, CreateSync, CreateTexture, + CreateTextureArray, GetCapabilities, Unused, PreFrame, @@ -25,6 +27,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading CounterEventDispose, CounterEventFlush, + ImageArraySetFormats, + ImageArraySetImages, + ProgramDispose, ProgramGetBinary, ProgramCheckLink, @@ -44,6 +49,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading TextureSetDataSliceRegion, TextureSetStorage, + TextureArraySetSamplers, + TextureArraySetTextures, + WindowPresent, Barrier, @@ -76,6 +84,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading SetTransformFeedbackBuffers, SetUniformBuffers, SetImage, + SetImageArray, SetIndexBuffer, SetLineParameters, SetLogicOpState, @@ -92,6 +101,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading SetScissor, SetStencilTest, SetTextureAndSampler, + SetTextureArray, SetUserClipDistance, SetVertexAttribs, SetVertexBuffers, diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArraySetFormatsCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArraySetFormatsCommand.cs new file mode 100644 index 0000000000..8e3ba88a88 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArraySetFormatsCommand.cs @@ -0,0 +1,26 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray +{ + struct ImageArraySetFormatsCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.ImageArraySetFormats; + private TableRef _imageArray; + private int _index; + private TableRef _imageFormats; + + public void Set(TableRef imageArray, int index, TableRef imageFormats) + { + _imageArray = imageArray; + _index = index; + _imageFormats = imageFormats; + } + + public static void Run(ref ImageArraySetFormatsCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedImageArray imageArray = command._imageArray.Get(threaded); + imageArray.Base.SetFormats(command._index, command._imageFormats.Get(threaded)); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArraySetImagesCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArraySetImagesCommand.cs new file mode 100644 index 0000000000..cc28d585c4 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArraySetImagesCommand.cs @@ -0,0 +1,27 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using System.Linq; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray +{ + struct ImageArraySetImagesCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.ImageArraySetImages; + private TableRef _imageArray; + private int _index; + private TableRef _images; + + public void Set(TableRef imageArray, int index, TableRef images) + { + _imageArray = imageArray; + _index = index; + _images = images; + } + + public static void Run(ref ImageArraySetImagesCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedImageArray imageArray = command._imageArray.Get(threaded); + imageArray.Base.SetImages(command._index, command._images.Get(threaded).Select(texture => ((ThreadedTexture)texture)?.Base).ToArray()); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateImageArrayCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateImageArrayCommand.cs new file mode 100644 index 0000000000..1c3fb81209 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateImageArrayCommand.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct CreateImageArrayCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.CreateImageArray; + private TableRef _imageArray; + private int _size; + private bool _isBuffer; + + public void Set(TableRef imageArray, int size, bool isBuffer) + { + _imageArray = imageArray; + _size = size; + _isBuffer = isBuffer; + } + + public static void Run(ref CreateImageArrayCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._imageArray.Get(threaded).Base = renderer.CreateImageArray(command._size, command._isBuffer); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateTextureArrayCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateTextureArrayCommand.cs new file mode 100644 index 0000000000..9bd891e68d --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateTextureArrayCommand.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct CreateTextureArrayCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.CreateTextureArray; + private TableRef _textureArray; + private int _size; + private bool _isBuffer; + + public void Set(TableRef textureArray, int size, bool isBuffer) + { + _textureArray = textureArray; + _size = size; + _isBuffer = isBuffer; + } + + public static void Run(ref CreateTextureArrayCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._textureArray.Get(threaded).Base = renderer.CreateTextureArray(command._size, command._isBuffer); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageArrayCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageArrayCommand.cs new file mode 100644 index 0000000000..b8d3c7ac5c --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageArrayCommand.cs @@ -0,0 +1,26 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using Ryujinx.Graphics.Shader; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetImageArrayCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetImageArray; + private ShaderStage _stage; + private int _binding; + private TableRef _array; + + public void Set(ShaderStage stage, int binding, TableRef array) + { + _stage = stage; + _binding = binding; + _array = array; + } + + public static void Run(ref SetImageArrayCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetImageArray(command._stage, command._binding, command._array.GetAs(threaded)?.Base); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureArrayCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureArrayCommand.cs new file mode 100644 index 0000000000..45e28aa658 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureArrayCommand.cs @@ -0,0 +1,26 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using Ryujinx.Graphics.Shader; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetTextureArrayCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetTextureArray; + private ShaderStage _stage; + private int _binding; + private TableRef _array; + + public void Set(ShaderStage stage, int binding, TableRef array) + { + _stage = stage; + _binding = binding; + _array = array; + } + + public static void Run(ref SetTextureArrayCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetTextureArray(command._stage, command._binding, command._array.GetAs(threaded)?.Base); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureAndSamplerArray/TextureArraySetSamplersCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureAndSamplerArray/TextureArraySetSamplersCommand.cs new file mode 100644 index 0000000000..204ee32da6 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureAndSamplerArray/TextureArraySetSamplersCommand.cs @@ -0,0 +1,27 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using System.Linq; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray +{ + struct TextureArraySetSamplersCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.TextureArraySetSamplers; + private TableRef _textureArray; + private int _index; + private TableRef _samplers; + + public void Set(TableRef textureArray, int index, TableRef samplers) + { + _textureArray = textureArray; + _index = index; + _samplers = samplers; + } + + public static void Run(ref TextureArraySetSamplersCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedTextureArray textureArray = command._textureArray.Get(threaded); + textureArray.Base.SetSamplers(command._index, command._samplers.Get(threaded).Select(sampler => ((ThreadedSampler)sampler)?.Base).ToArray()); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureAndSamplerArray/TextureArraySetTexturesCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureAndSamplerArray/TextureArraySetTexturesCommand.cs new file mode 100644 index 0000000000..cc94d1b6d1 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureAndSamplerArray/TextureArraySetTexturesCommand.cs @@ -0,0 +1,27 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using System.Linq; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray +{ + struct TextureArraySetTexturesCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.TextureArraySetTextures; + private TableRef _textureArray; + private int _index; + private TableRef _textures; + + public void Set(TableRef textureArray, int index, TableRef textures) + { + _textureArray = textureArray; + _index = index; + _textures = textures; + } + + public static void Run(ref TextureArraySetTexturesCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedTextureArray textureArray = command._textureArray.Get(threaded); + textureArray.Base.SetTextures(command._index, command._textures.Get(threaded).Select(texture => ((ThreadedTexture)texture)?.Base).ToArray()); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedImageArray.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedImageArray.cs new file mode 100644 index 0000000000..d26ee1fbd5 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedImageArray.cs @@ -0,0 +1,36 @@ +using Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray; +using Ryujinx.Graphics.GAL.Multithreading.Model; + +namespace Ryujinx.Graphics.GAL.Multithreading.Resources +{ + /// + /// Threaded representation of a image array. + /// + class ThreadedImageArray : IImageArray + { + private readonly ThreadedRenderer _renderer; + public IImageArray Base; + + public ThreadedImageArray(ThreadedRenderer renderer) + { + _renderer = renderer; + } + + private TableRef Ref(T reference) + { + return new TableRef(_renderer, reference); + } + + public void SetFormats(int index, Format[] imageFormats) + { + _renderer.New().Set(Ref(this), index, Ref(imageFormats)); + _renderer.QueueCommand(); + } + + public void SetImages(int index, ITexture[] images) + { + _renderer.New().Set(Ref(this), index, Ref(images)); + _renderer.QueueCommand(); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTextureArray.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTextureArray.cs new file mode 100644 index 0000000000..82405a1f69 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTextureArray.cs @@ -0,0 +1,37 @@ +using Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray; +using Ryujinx.Graphics.GAL.Multithreading.Model; +using System.Linq; + +namespace Ryujinx.Graphics.GAL.Multithreading.Resources +{ + /// + /// Threaded representation of a texture and sampler array. + /// + class ThreadedTextureArray : ITextureArray + { + private readonly ThreadedRenderer _renderer; + public ITextureArray Base; + + public ThreadedTextureArray(ThreadedRenderer renderer) + { + _renderer = renderer; + } + + private TableRef Ref(T reference) + { + return new TableRef(_renderer, reference); + } + + public void SetSamplers(int index, ISampler[] samplers) + { + _renderer.New().Set(Ref(this), index, Ref(samplers.ToArray())); + _renderer.QueueCommand(); + } + + public void SetTextures(int index, ITexture[] textures) + { + _renderer.New().Set(Ref(this), index, Ref(textures.ToArray())); + _renderer.QueueCommand(); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs index ad50bddf44..697894eb52 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs @@ -183,6 +183,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading _renderer.QueueCommand(); } + public void SetImageArray(ShaderStage stage, int binding, IImageArray array) + { + _renderer.New().Set(stage, binding, Ref(array)); + _renderer.QueueCommand(); + } + public void SetIndexBuffer(BufferRange buffer, IndexType type) { _renderer.New().Set(buffer, type); @@ -285,6 +291,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading _renderer.QueueCommand(); } + public void SetTextureArray(ShaderStage stage, int binding, ITextureArray array) + { + _renderer.New().Set(stage, binding, Ref(array)); + _renderer.QueueCommand(); + } + public void SetTransformFeedbackBuffers(ReadOnlySpan buffers) { _renderer.New().Set(_renderer.CopySpan(buffers)); diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs index 830fbf2d91..5e17bcd2c1 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs @@ -299,6 +299,15 @@ namespace Ryujinx.Graphics.GAL.Multithreading return handle; } + public IImageArray CreateImageArray(int size, bool isBuffer) + { + var imageArray = new ThreadedImageArray(this); + New().Set(Ref(imageArray), size, isBuffer); + QueueCommand(); + + return imageArray; + } + public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info) { var program = new ThreadedProgram(this); @@ -349,6 +358,14 @@ namespace Ryujinx.Graphics.GAL.Multithreading return texture; } } + public ITextureArray CreateTextureArray(int size, bool isBuffer) + { + var textureArray = new ThreadedTextureArray(this); + New().Set(Ref(textureArray), size, isBuffer); + QueueCommand(); + + return textureArray; + } public void DeleteBuffer(BufferHandle buffer) { diff --git a/src/Ryujinx.Graphics.GAL/ResourceLayout.cs b/src/Ryujinx.Graphics.GAL/ResourceLayout.cs index 84bca5b41d..998c046f19 100644 --- a/src/Ryujinx.Graphics.GAL/ResourceLayout.cs +++ b/src/Ryujinx.Graphics.GAL/ResourceLayout.cs @@ -71,19 +71,21 @@ namespace Ryujinx.Graphics.GAL public readonly struct ResourceUsage : IEquatable { public int Binding { get; } + public int ArrayLength { get; } public ResourceType Type { get; } public ResourceStages Stages { get; } - public ResourceUsage(int binding, ResourceType type, ResourceStages stages) + public ResourceUsage(int binding, int arrayLength, ResourceType type, ResourceStages stages) { Binding = binding; + ArrayLength = arrayLength; Type = type; Stages = stages; } public override int GetHashCode() { - return HashCode.Combine(Binding, Type, Stages); + return HashCode.Combine(Binding, ArrayLength, Type, Stages); } public override bool Equals(object obj) @@ -93,7 +95,7 @@ namespace Ryujinx.Graphics.GAL public bool Equals(ResourceUsage other) { - return Binding == other.Binding && Type == other.Type && Stages == other.Stages; + return Binding == other.Binding && ArrayLength == other.ArrayLength && Type == other.Type && Stages == other.Stages; } public static bool operator ==(ResourceUsage left, ResourceUsage right) diff --git a/src/Ryujinx.Graphics.Gpu/Constants.cs b/src/Ryujinx.Graphics.Gpu/Constants.cs index c553d988e9..23b9be5ca7 100644 --- a/src/Ryujinx.Graphics.Gpu/Constants.cs +++ b/src/Ryujinx.Graphics.Gpu/Constants.cs @@ -89,5 +89,10 @@ namespace Ryujinx.Graphics.Gpu /// Maximum size that an storage buffer is assumed to have when the correct size is unknown. ///
public const ulong MaxUnknownStorageSize = 0x100000; + + /// + /// Size of a bindless texture handle as exposed by guest graphics APIs. + /// + public const int TextureHandleSizeInBytes = sizeof(ulong); } } diff --git a/src/Ryujinx.Graphics.Gpu/Engine/ShaderTexture.cs b/src/Ryujinx.Graphics.Gpu/Engine/ShaderTexture.cs index 492c6ee60a..7bff1c4b82 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/ShaderTexture.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/ShaderTexture.cs @@ -16,7 +16,7 @@ namespace Ryujinx.Graphics.Gpu.Engine /// Texture target value public static Target GetTarget(SamplerType type) { - type &= ~(SamplerType.Indexed | SamplerType.Shadow); + type &= ~SamplerType.Shadow; switch (type) { diff --git a/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs b/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs index 732ec5d4c8..5e66a3b543 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs @@ -107,8 +107,7 @@ namespace Ryujinx.Graphics.Gpu.Image if (texture.CacheNode != _textures.Last) { _textures.Remove(texture.CacheNode); - - texture.CacheNode = _textures.AddLast(texture); + _textures.AddLast(texture.CacheNode); } if (_totalSize > MaxTextureSizeCapacity && _textures.Count >= MinCountForDeletion) diff --git a/src/Ryujinx.Graphics.Gpu/Image/Pool.cs b/src/Ryujinx.Graphics.Gpu/Image/Pool.cs index 6ede019710..e12fedc746 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/Pool.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/Pool.cs @@ -111,6 +111,21 @@ namespace Ryujinx.Graphics.Gpu.Image /// The GPU resource with the given ID public abstract T1 Get(int id); + /// + /// Gets the cached item with the given ID, or null if there is no cached item for the specified ID. + /// + /// ID of the item. This is effectively a zero-based index + /// The cached item with the given ID + public T1 GetCachedItem(int id) + { + if (!IsValidId(id)) + { + return default; + } + + return Items[id]; + } + /// /// Checks if a given ID is valid and inside the range of the pool. /// @@ -197,6 +212,23 @@ namespace Ryujinx.Graphics.Gpu.Image return false; } + /// + /// Checks if the pool was modified by comparing the current with a cached one. + /// + /// Cached modified sequence number + /// True if the pool was modified, false otherwise + public bool WasModified(ref int sequenceNumber) + { + if (sequenceNumber != ModifiedSequenceNumber) + { + sequenceNumber = ModifiedSequenceNumber; + + return true; + } + + return false; + } + protected abstract void InvalidateRangeImpl(ulong address, ulong size); protected abstract void Delete(T1 item); diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs index 606842d6d2..12a457dbcb 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs @@ -24,6 +24,11 @@ namespace Ryujinx.Graphics.Gpu.Image ///
public int Binding { get; } + /// + /// For array of textures, this indicates the length of the array. A value of one indicates it is not an array. + /// + public int ArrayLength { get; } + /// /// Constant buffer slot with the texture handle. /// @@ -45,14 +50,16 @@ namespace Ryujinx.Graphics.Gpu.Image /// The shader sampler target type /// Format of the image as declared on the shader /// The shader texture binding point + /// For array of textures, this indicates the length of the array. A value of one indicates it is not an array /// Constant buffer slot where the texture handle is located /// The shader texture handle (read index into the texture constant buffer) /// The texture's usage flags, indicating how it is used in the shader - public TextureBindingInfo(Target target, Format format, int binding, int cbufSlot, int handle, TextureUsageFlags flags) + public TextureBindingInfo(Target target, Format format, int binding, int arrayLength, int cbufSlot, int handle, TextureUsageFlags flags) { Target = target; Format = format; Binding = binding; + ArrayLength = arrayLength; CbufSlot = cbufSlot; Handle = handle; Flags = flags; @@ -63,10 +70,11 @@ namespace Ryujinx.Graphics.Gpu.Image ///
/// The shader sampler target type /// The shader texture binding point + /// For array of textures, this indicates the length of the array. A value of one indicates it is not an array /// Constant buffer slot where the texture handle is located /// The shader texture handle (read index into the texture constant buffer) /// The texture's usage flags, indicating how it is used in the shader - public TextureBindingInfo(Target target, int binding, int cbufSlot, int handle, TextureUsageFlags flags) : this(target, (Format)0, binding, cbufSlot, handle, flags) + public TextureBindingInfo(Target target, int binding, int arrayLength, int cbufSlot, int handle, TextureUsageFlags flags) : this(target, (Format)0, binding, arrayLength, cbufSlot, handle, flags) { } } diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs new file mode 100644 index 0000000000..70ea1f6b91 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs @@ -0,0 +1,714 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.Types; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Shader; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Texture bindings array cache. + /// + class TextureBindingsArrayCache + { + /// + /// Minimum timestamp delta until texture array can be removed from the cache. + /// + private const int MinDeltaForRemoval = 20000; + + private readonly GpuContext _context; + private readonly GpuChannel _channel; + private readonly bool _isCompute; + + /// + /// Array cache entry key. + /// + private readonly struct CacheEntryKey : IEquatable + { + /// + /// Whether the entry is for an image. + /// + public readonly bool IsImage; + + /// + /// Texture or image target type. + /// + public readonly Target Target; + + /// + /// Word offset of the first handle on the constant buffer. + /// + public readonly int HandleIndex; + + /// + /// Number of entries of the array. + /// + public readonly int ArrayLength; + + private readonly TexturePool _texturePool; + private readonly SamplerPool _samplerPool; + + private readonly BufferBounds _textureBufferBounds; + + /// + /// Creates a new array cache entry. + /// + /// Whether the entry is for an image + /// Binding information for the array + /// Texture pool where the array textures are located + /// Sampler pool where the array samplers are located + /// Constant buffer bounds with the texture handles + public CacheEntryKey( + bool isImage, + TextureBindingInfo bindingInfo, + TexturePool texturePool, + SamplerPool samplerPool, + ref BufferBounds textureBufferBounds) + { + IsImage = isImage; + Target = bindingInfo.Target; + HandleIndex = bindingInfo.Handle; + ArrayLength = bindingInfo.ArrayLength; + + _texturePool = texturePool; + _samplerPool = samplerPool; + + _textureBufferBounds = textureBufferBounds; + } + + /// + /// Checks if the texture and sampler pools matches the cached pools. + /// + /// Texture pool instance + /// Sampler pool instance + /// True if the pools match, false otherwise + private bool MatchesPools(TexturePool texturePool, SamplerPool samplerPool) + { + return _texturePool == texturePool && _samplerPool == samplerPool; + } + + /// + /// Checks if the cached constant buffer address and size matches. + /// + /// New buffer address and size + /// True if the address and size matches, false otherwise + private bool MatchesBufferBounds(BufferBounds textureBufferBounds) + { + return _textureBufferBounds.Equals(textureBufferBounds); + } + + public bool Equals(CacheEntryKey other) + { + return IsImage == other.IsImage && + Target == other.Target && + HandleIndex == other.HandleIndex && + ArrayLength == other.ArrayLength && + MatchesPools(other._texturePool, other._samplerPool) && + MatchesBufferBounds(other._textureBufferBounds); + } + + public override bool Equals(object obj) + { + return obj is CacheEntryKey other && Equals(other); + } + + public override int GetHashCode() + { + return _textureBufferBounds.Range.GetHashCode(); + } + } + + /// + /// Array cache entry. + /// + private class CacheEntry + { + /// + /// Key for this entry on the cache. + /// + public readonly CacheEntryKey Key; + + /// + /// Linked list node used on the texture bindings array cache. + /// + public LinkedListNode CacheNode; + + /// + /// Timestamp set on the last use of the array by the cache. + /// + public int CacheTimestamp; + + /// + /// All cached textures, along with their invalidated sequence number as value. + /// + public readonly Dictionary Textures; + + /// + /// All pool texture IDs along with their textures. + /// + public readonly Dictionary TextureIds; + + /// + /// All pool sampler IDs along with their samplers. + /// + public readonly Dictionary SamplerIds; + + /// + /// Backend texture array if the entry is for a texture, otherwise null. + /// + public readonly ITextureArray TextureArray; + + /// + /// Backend image array if the entry is for an image, otherwise null. + /// + public readonly IImageArray ImageArray; + + private readonly TexturePool _texturePool; + private readonly SamplerPool _samplerPool; + + private int _texturePoolSequence; + private int _samplerPoolSequence; + + private int[] _cachedTextureBuffer; + private int[] _cachedSamplerBuffer; + + private int _lastSequenceNumber; + + /// + /// Creates a new array cache entry. + /// + /// Key for this entry on the cache + /// Texture pool where the array textures are located + /// Sampler pool where the array samplers are located + private CacheEntry(ref CacheEntryKey key, TexturePool texturePool, SamplerPool samplerPool) + { + Key = key; + Textures = new Dictionary(); + TextureIds = new Dictionary(); + SamplerIds = new Dictionary(); + + _texturePool = texturePool; + _samplerPool = samplerPool; + + _lastSequenceNumber = -1; + } + + /// + /// Creates a new array cache entry. + /// + /// Key for this entry on the cache + /// Backend texture array + /// Texture pool where the array textures are located + /// Sampler pool where the array samplers are located + public CacheEntry(ref CacheEntryKey key, ITextureArray array, TexturePool texturePool, SamplerPool samplerPool) : this(ref key, texturePool, samplerPool) + { + TextureArray = array; + } + + /// + /// Creates a new array cache entry. + /// + /// Key for this entry on the cache + /// Backend image array + /// Texture pool where the array textures are located + /// Sampler pool where the array samplers are located + public CacheEntry(ref CacheEntryKey key, IImageArray array, TexturePool texturePool, SamplerPool samplerPool) : this(ref key, texturePool, samplerPool) + { + ImageArray = array; + } + + /// + /// Synchronizes memory for all textures in the array. + /// + /// Indicates if the texture may be modified by the access + public void SynchronizeMemory(bool isStore) + { + foreach (Texture texture in Textures.Keys) + { + texture.SynchronizeMemory(); + + if (isStore) + { + texture.SignalModified(); + } + } + } + + /// + /// Clears all cached texture instances. + /// + public void Reset() + { + Textures.Clear(); + TextureIds.Clear(); + SamplerIds.Clear(); + } + + /// + /// Updates the cached constant buffer data. + /// + /// Constant buffer data with the texture handles (and sampler handles, if they are combined) + /// Constant buffer data with the sampler handles + /// Whether and comes from different buffers + public void UpdateData(ReadOnlySpan cachedTextureBuffer, ReadOnlySpan cachedSamplerBuffer, bool separateSamplerBuffer) + { + _cachedTextureBuffer = cachedTextureBuffer.ToArray(); + _cachedSamplerBuffer = separateSamplerBuffer ? cachedSamplerBuffer.ToArray() : _cachedTextureBuffer; + } + + /// + /// Checks if any texture has been deleted since the last call to this method. + /// + /// True if one or more textures have been deleted, false otherwise + public bool ValidateTextures() + { + foreach ((Texture texture, int invalidatedSequence) in Textures) + { + if (texture.InvalidatedSequence != invalidatedSequence) + { + return false; + } + } + + return true; + } + + /// + /// Checks if the cached texture or sampler pool has been modified since the last call to this method. + /// + /// True if any used entries of the pools might have been modified, false otherwise + public bool PoolsModified() + { + bool texturePoolModified = _texturePool.WasModified(ref _texturePoolSequence); + bool samplerPoolModified = _samplerPool.WasModified(ref _samplerPoolSequence); + + // If both pools were not modified since the last check, we have nothing else to check. + if (!texturePoolModified && !samplerPoolModified) + { + return false; + } + + // If the pools were modified, let's check if any of the entries we care about changed. + + // Check if any of our cached textures changed on the pool. + foreach ((int textureId, Texture texture) in TextureIds) + { + if (_texturePool.GetCachedItem(textureId) != texture) + { + return true; + } + } + + // Check if any of our cached samplers changed on the pool. + foreach ((int samplerId, Sampler sampler) in SamplerIds) + { + if (_samplerPool.GetCachedItem(samplerId) != sampler) + { + return true; + } + } + + return false; + } + + /// + /// Checks if the sequence number matches the one used on the last call to this method. + /// + /// Current sequence number + /// True if the sequence numbers match, false otherwise + public bool MatchesSequenceNumber(int currentSequenceNumber) + { + if (_lastSequenceNumber == currentSequenceNumber) + { + return true; + } + + _lastSequenceNumber = currentSequenceNumber; + + return false; + } + + /// + /// Checks if the buffer data matches the cached data. + /// + /// New texture buffer data + /// New sampler buffer data + /// Whether and comes from different buffers + /// Word offset of the sampler constant buffer handle that is used + /// True if the data matches, false otherwise + public bool MatchesBufferData( + ReadOnlySpan cachedTextureBuffer, + ReadOnlySpan cachedSamplerBuffer, + bool separateSamplerBuffer, + int samplerWordOffset) + { + if (_cachedTextureBuffer != null && cachedTextureBuffer.Length > _cachedTextureBuffer.Length) + { + cachedTextureBuffer = cachedTextureBuffer[.._cachedTextureBuffer.Length]; + } + + if (!_cachedTextureBuffer.AsSpan().SequenceEqual(cachedTextureBuffer)) + { + return false; + } + + if (separateSamplerBuffer) + { + if (_cachedSamplerBuffer == null || + _cachedSamplerBuffer.Length <= samplerWordOffset || + cachedSamplerBuffer.Length <= samplerWordOffset) + { + return false; + } + + int oldValue = _cachedSamplerBuffer[samplerWordOffset]; + int newValue = cachedSamplerBuffer[samplerWordOffset]; + + return oldValue == newValue; + } + + return true; + } + } + + private readonly Dictionary _cache; + private readonly LinkedList _lruCache; + + private int _currentTimestamp; + + /// + /// Creates a new instance of the texture bindings array cache. + /// + /// GPU context + /// GPU channel + /// Whether the bindings will be used for compute or graphics pipelines + public TextureBindingsArrayCache(GpuContext context, GpuChannel channel, bool isCompute) + { + _context = context; + _channel = channel; + _isCompute = isCompute; + _cache = new Dictionary(); + _lruCache = new LinkedList(); + } + + /// + /// Updates a texture array bindings and textures. + /// + /// Texture pool + /// Sampler pool + /// Shader stage where the array is used + /// Shader stage index where the array is used + /// Texture constant buffer index + /// Sampler handles source + /// Array binding information + public void UpdateTextureArray( + TexturePool texturePool, + SamplerPool samplerPool, + ShaderStage stage, + int stageIndex, + int textureBufferIndex, + SamplerIndex samplerIndex, + TextureBindingInfo bindingInfo) + { + Update(texturePool, samplerPool, stage, stageIndex, textureBufferIndex, isImage: false, samplerIndex, bindingInfo); + } + + /// + /// Updates a image array bindings and textures. + /// + /// Texture pool + /// Shader stage where the array is used + /// Shader stage index where the array is used + /// Texture constant buffer index + /// Array binding information + public void UpdateImageArray(TexturePool texturePool, ShaderStage stage, int stageIndex, int textureBufferIndex, TextureBindingInfo bindingInfo) + { + Update(texturePool, null, stage, stageIndex, textureBufferIndex, isImage: true, SamplerIndex.ViaHeaderIndex, bindingInfo); + } + + /// + /// Updates a texture or image array bindings and textures. + /// + /// Texture pool + /// Sampler pool + /// Shader stage where the array is used + /// Shader stage index where the array is used + /// Texture constant buffer index + /// Whether the array is a image or texture array + /// Sampler handles source + /// Array binding information + private void Update( + TexturePool texturePool, + SamplerPool samplerPool, + ShaderStage stage, + int stageIndex, + int textureBufferIndex, + bool isImage, + SamplerIndex samplerIndex, + TextureBindingInfo bindingInfo) + { + (textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, textureBufferIndex); + + bool separateSamplerBuffer = textureBufferIndex != samplerBufferIndex; + + ref BufferBounds textureBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, textureBufferIndex); + ref BufferBounds samplerBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, samplerBufferIndex); + + CacheEntry entry = GetOrAddEntry( + texturePool, + samplerPool, + bindingInfo, + isImage, + ref textureBufferBounds, + out bool isNewEntry); + + bool poolsModified = entry.PoolsModified(); + bool isStore = bindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore); + + ReadOnlySpan cachedTextureBuffer; + ReadOnlySpan cachedSamplerBuffer; + + if (!poolsModified && !isNewEntry && entry.ValidateTextures()) + { + if (entry.MatchesSequenceNumber(_context.SequenceNumber)) + { + entry.SynchronizeMemory(isStore); + + if (isImage) + { + _context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray); + } + else + { + _context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray); + } + + return; + } + + cachedTextureBuffer = MemoryMarshal.Cast(_channel.MemoryManager.Physical.GetSpan(textureBufferBounds.Range)); + + if (separateSamplerBuffer) + { + cachedSamplerBuffer = MemoryMarshal.Cast(_channel.MemoryManager.Physical.GetSpan(samplerBufferBounds.Range)); + } + else + { + cachedSamplerBuffer = cachedTextureBuffer; + } + + (_, int samplerWordOffset, _) = TextureHandle.UnpackOffsets(bindingInfo.Handle); + + if (entry.MatchesBufferData(cachedTextureBuffer, cachedSamplerBuffer, separateSamplerBuffer, samplerWordOffset)) + { + entry.SynchronizeMemory(isStore); + + if (isImage) + { + _context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray); + } + else + { + _context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray); + } + + return; + } + } + else + { + cachedTextureBuffer = MemoryMarshal.Cast(_channel.MemoryManager.Physical.GetSpan(textureBufferBounds.Range)); + + if (separateSamplerBuffer) + { + cachedSamplerBuffer = MemoryMarshal.Cast(_channel.MemoryManager.Physical.GetSpan(samplerBufferBounds.Range)); + } + else + { + cachedSamplerBuffer = cachedTextureBuffer; + } + } + + if (!isNewEntry) + { + entry.Reset(); + } + + entry.UpdateData(cachedTextureBuffer, cachedSamplerBuffer, separateSamplerBuffer); + + Format[] formats = isImage ? new Format[bindingInfo.ArrayLength] : null; + ISampler[] samplers = isImage ? null : new ISampler[bindingInfo.ArrayLength]; + ITexture[] textures = new ITexture[bindingInfo.ArrayLength]; + + for (int index = 0; index < bindingInfo.ArrayLength; index++) + { + int handleIndex = bindingInfo.Handle + index * (Constants.TextureHandleSizeInBytes / sizeof(int)); + int packedId = TextureHandle.ReadPackedId(handleIndex, cachedTextureBuffer, cachedSamplerBuffer); + int textureId = TextureHandle.UnpackTextureId(packedId); + int samplerId; + + if (samplerIndex == SamplerIndex.ViaHeaderIndex) + { + samplerId = textureId; + } + else + { + samplerId = TextureHandle.UnpackSamplerId(packedId); + } + + ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(textureId, out Texture texture); + + if (texture != null) + { + entry.Textures[texture] = texture.InvalidatedSequence; + + if (isStore) + { + texture.SignalModified(); + } + } + + Sampler sampler = samplerPool?.Get(samplerId); + + entry.TextureIds[textureId] = texture; + entry.SamplerIds[samplerId] = sampler; + + ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target); + ISampler hostSampler = sampler?.GetHostSampler(texture); + + Format format = bindingInfo.Format; + + if (hostTexture != null && texture.Target == Target.TextureBuffer) + { + // Ensure that the buffer texture is using the correct buffer as storage. + // Buffers are frequently re-created to accommodate larger data, so we need to re-bind + // to ensure we're not using a old buffer that was already deleted. + if (isImage) + { + if (format == 0 && texture != null) + { + format = texture.Format; + } + + _channel.BufferManager.SetBufferTextureStorage(entry.ImageArray, hostTexture, texture.Range, bindingInfo, index, format); + } + else + { + _channel.BufferManager.SetBufferTextureStorage(entry.TextureArray, hostTexture, texture.Range, bindingInfo, index, format); + } + } + else if (isImage) + { + if (format == 0 && texture != null) + { + format = texture.Format; + } + + formats[index] = format; + textures[index] = hostTexture; + } + else + { + samplers[index] = hostSampler; + textures[index] = hostTexture; + } + } + + if (isImage) + { + entry.ImageArray.SetFormats(0, formats); + entry.ImageArray.SetImages(0, textures); + + _context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray); + } + else + { + entry.TextureArray.SetSamplers(0, samplers); + entry.TextureArray.SetTextures(0, textures); + + _context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray); + } + } + + /// + /// Gets a cached texture entry, or creates a new one if not found. + /// + /// Texture pool + /// Sampler pool + /// Array binding information + /// Whether the array is a image or texture array + /// Constant buffer bounds with the texture handles + /// Whether a new entry was created, or an existing one was returned + /// Cache entry + private CacheEntry GetOrAddEntry( + TexturePool texturePool, + SamplerPool samplerPool, + TextureBindingInfo bindingInfo, + bool isImage, + ref BufferBounds textureBufferBounds, + out bool isNew) + { + CacheEntryKey key = new CacheEntryKey( + isImage, + bindingInfo, + texturePool, + samplerPool, + ref textureBufferBounds); + + isNew = !_cache.TryGetValue(key, out CacheEntry entry); + + if (isNew) + { + int arrayLength = bindingInfo.ArrayLength; + + if (isImage) + { + IImageArray array = _context.Renderer.CreateImageArray(arrayLength, bindingInfo.Target == Target.TextureBuffer); + + _cache.Add(key, entry = new CacheEntry(ref key, array, texturePool, samplerPool)); + } + else + { + ITextureArray array = _context.Renderer.CreateTextureArray(arrayLength, bindingInfo.Target == Target.TextureBuffer); + + _cache.Add(key, entry = new CacheEntry(ref key, array, texturePool, samplerPool)); + } + } + + if (entry.CacheNode != null) + { + _lruCache.Remove(entry.CacheNode); + _lruCache.AddLast(entry.CacheNode); + } + else + { + entry.CacheNode = _lruCache.AddLast(entry); + } + + entry.CacheTimestamp = ++_currentTimestamp; + + RemoveLeastUsedEntries(); + + return entry; + } + + /// + /// Remove entries from the cache that have not been used for some time. + /// + private void RemoveLeastUsedEntries() + { + LinkedListNode nextNode = _lruCache.First; + + while (nextNode != null && _currentTimestamp - nextNode.Value.CacheTimestamp >= MinDeltaForRemoval) + { + LinkedListNode toRemove = nextNode; + nextNode = nextNode.Next; + _cache.Remove(toRemove.Value.Key); + _lruCache.Remove(toRemove); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs index ef5d0deaad..3c10c95e02 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs @@ -34,6 +34,8 @@ namespace Ryujinx.Graphics.Gpu.Image private readonly TexturePoolCache _texturePoolCache; private readonly SamplerPoolCache _samplerPoolCache; + private readonly TextureBindingsArrayCache _arrayBindingsCache; + private TexturePool _cachedTexturePool; private SamplerPool _cachedSamplerPool; @@ -56,6 +58,8 @@ namespace Ryujinx.Graphics.Gpu.Image private TextureState[] _textureState; private TextureState[] _imageState; + private int[] _textureCounts; + private int _texturePoolSequence; private int _samplerPoolSequence; @@ -85,6 +89,8 @@ namespace Ryujinx.Graphics.Gpu.Image _isCompute = isCompute; + _arrayBindingsCache = new TextureBindingsArrayCache(context, channel, isCompute); + int stages = isCompute ? 1 : Constants.ShaderStages; _textureBindings = new TextureBindingInfo[stages][]; @@ -95,9 +101,11 @@ namespace Ryujinx.Graphics.Gpu.Image for (int stage = 0; stage < stages; stage++) { - _textureBindings[stage] = new TextureBindingInfo[InitialTextureStateSize]; - _imageBindings[stage] = new TextureBindingInfo[InitialImageStateSize]; + _textureBindings[stage] = Array.Empty(); + _imageBindings[stage] = Array.Empty(); } + + _textureCounts = Array.Empty(); } /// @@ -109,6 +117,8 @@ namespace Ryujinx.Graphics.Gpu.Image _textureBindings = bindings.TextureBindings; _imageBindings = bindings.ImageBindings; + _textureCounts = bindings.TextureCounts; + SetMaxBindings(bindings.MaxTextureBinding, bindings.MaxImageBinding); } @@ -401,27 +411,6 @@ namespace Ryujinx.Graphics.Gpu.Image } } -#pragma warning disable IDE0051 // Remove unused private member - /// - /// Counts the total number of texture bindings used by all shader stages. - /// - /// The total amount of textures used - private int GetTextureBindingsCount() - { - int count = 0; - - foreach (TextureBindingInfo[] textureInfo in _textureBindings) - { - if (textureInfo != null) - { - count += textureInfo.Length; - } - } - - return count; - } -#pragma warning restore IDE0051 - /// /// Ensures that the texture bindings are visible to the host GPU. /// Note: this actually performs the binding using the host graphics API. @@ -465,6 +454,13 @@ namespace Ryujinx.Graphics.Gpu.Image TextureBindingInfo bindingInfo = _textureBindings[stageIndex][index]; TextureUsageFlags usageFlags = bindingInfo.Flags; + if (bindingInfo.ArrayLength > 1) + { + _arrayBindingsCache.UpdateTextureArray(texturePool, samplerPool, stage, stageIndex, _textureBufferIndex, _samplerIndex, bindingInfo); + + continue; + } + (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex); UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex); @@ -582,7 +578,7 @@ namespace Ryujinx.Graphics.Gpu.Image } // Scales for images appear after the texture ones. - int baseScaleIndex = _textureBindings[stageIndex].Length; + int baseScaleIndex = _textureCounts[stageIndex]; int cachedTextureBufferIndex = -1; int cachedSamplerBufferIndex = -1; @@ -595,6 +591,14 @@ namespace Ryujinx.Graphics.Gpu.Image { TextureBindingInfo bindingInfo = _imageBindings[stageIndex][index]; TextureUsageFlags usageFlags = bindingInfo.Flags; + + if (bindingInfo.ArrayLength > 1) + { + _arrayBindingsCache.UpdateImageArray(pool, stage, stageIndex, _textureBufferIndex, bindingInfo); + + continue; + } + int scaleIndex = baseScaleIndex + index; (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex); @@ -620,7 +624,7 @@ namespace Ryujinx.Graphics.Gpu.Image if (isStore) { - cachedTexture?.SignalModified(); + cachedTexture.SignalModified(); } Format format = bindingInfo.Format == 0 ? cachedTexture.Format : bindingInfo.Format; diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs index aed3268aec..cf783ef2f2 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs @@ -1,12 +1,13 @@ using Ryujinx.Graphics.Shader; using Ryujinx.Memory.Range; +using System; namespace Ryujinx.Graphics.Gpu.Memory { /// /// Memory range used for buffers. /// - readonly struct BufferBounds + readonly struct BufferBounds : IEquatable { /// /// Physical memory ranges where the buffer is mapped. @@ -33,5 +34,25 @@ namespace Ryujinx.Graphics.Gpu.Memory Range = range; Flags = flags; } + + public override bool Equals(object obj) + { + return obj is BufferBounds bounds && Equals(bounds); + } + + public bool Equals(BufferBounds bounds) + { + return Range == bounds.Range && Flags == bounds.Flags; + } + + public bool Equals(ref BufferBounds bounds) + { + return Range == bounds.Range && Flags == bounds.Flags; + } + + public override int GetHashCode() + { + return HashCode.Combine(Range, Flags); + } } } diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs index 1f02b9d7f1..8f2201e0aa 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs @@ -27,6 +27,8 @@ namespace Ryujinx.Graphics.Gpu.Memory private readonly VertexBuffer[] _vertexBuffers; private readonly BufferBounds[] _transformFeedbackBuffers; private readonly List _bufferTextures; + private readonly List> _bufferTextureArrays; + private readonly List> _bufferImageArrays; private readonly BufferAssignment[] _ranges; /// @@ -140,11 +142,12 @@ namespace Ryujinx.Graphics.Gpu.Memory } _bufferTextures = new List(); + _bufferTextureArrays = new List>(); + _bufferImageArrays = new List>(); _ranges = new BufferAssignment[Constants.TotalGpUniformBuffers * Constants.ShaderStages]; } - /// /// Sets the memory range with the index buffer data, to be used for subsequent draw calls. /// @@ -418,6 +421,16 @@ namespace Ryujinx.Graphics.Gpu.Memory return _cpUniformBuffers.Buffers[index].Range.GetSubRange(0).Address; } + /// + /// Gets the size of the compute uniform buffer currently bound at the given index. + /// + /// Index of the uniform buffer binding + /// The uniform buffer size, or an undefined value if the buffer is not currently bound + public int GetComputeUniformBufferSize(int index) + { + return (int)_cpUniformBuffers.Buffers[index].Range.GetSubRange(0).Size; + } + /// /// Gets the address of the graphics uniform buffer currently bound at the given index. /// @@ -429,6 +442,17 @@ namespace Ryujinx.Graphics.Gpu.Memory return _gpUniformBuffers[stage].Buffers[index].Range.GetSubRange(0).Address; } + /// + /// Gets the size of the graphics uniform buffer currently bound at the given index. + /// + /// Index of the shader stage + /// Index of the uniform buffer binding + /// The uniform buffer size, or an undefined value if the buffer is not currently bound + public int GetGraphicsUniformBufferSize(int stage, int index) + { + return (int)_gpUniformBuffers[stage].Buffers[index].Range.GetSubRange(0).Size; + } + /// /// Gets the bounds of the uniform buffer currently bound at the given index. /// @@ -459,7 +483,7 @@ namespace Ryujinx.Graphics.Gpu.Memory BindBuffers(bufferCache, _cpStorageBuffers, isStorage: true); BindBuffers(bufferCache, _cpUniformBuffers, isStorage: false); - CommitBufferTextureBindings(); + CommitBufferTextureBindings(bufferCache); // Force rebind after doing compute work. Rebind(); @@ -470,14 +494,15 @@ namespace Ryujinx.Graphics.Gpu.Memory /// /// Commit any queued buffer texture bindings. /// - private void CommitBufferTextureBindings() + /// Buffer cache + private void CommitBufferTextureBindings(BufferCache bufferCache) { if (_bufferTextures.Count > 0) { foreach (var binding in _bufferTextures) { var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore); - var range = _channel.MemoryManager.Physical.BufferCache.GetBufferRange(binding.Range, isStore); + var range = bufferCache.GetBufferRange(binding.Range, isStore); binding.Texture.SetStorage(range); // The texture must be rebound to use the new storage if it was updated. @@ -494,6 +519,33 @@ namespace Ryujinx.Graphics.Gpu.Memory _bufferTextures.Clear(); } + + if (_bufferTextureArrays.Count > 0 || _bufferImageArrays.Count > 0) + { + ITexture[] textureArray = new ITexture[1]; + + foreach (var binding in _bufferTextureArrays) + { + var range = bufferCache.GetBufferRange(binding.Range); + binding.Texture.SetStorage(range); + + textureArray[0] = binding.Texture; + binding.Array.SetTextures(binding.Index, textureArray); + } + + foreach (var binding in _bufferImageArrays) + { + var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore); + var range = bufferCache.GetBufferRange(binding.Range, isStore); + binding.Texture.SetStorage(range); + + textureArray[0] = binding.Texture; + binding.Array.SetImages(binding.Index, textureArray); + } + + _bufferTextureArrays.Clear(); + _bufferImageArrays.Clear(); + } } /// @@ -676,7 +728,7 @@ namespace Ryujinx.Graphics.Gpu.Memory UpdateBuffers(_gpUniformBuffers); } - CommitBufferTextureBindings(); + CommitBufferTextureBindings(bufferCache); _rebind = false; @@ -828,6 +880,50 @@ namespace Ryujinx.Graphics.Gpu.Memory _bufferTextures.Add(new BufferTextureBinding(stage, texture, range, bindingInfo, format, isImage)); } + /// + /// Sets the buffer storage of a buffer texture array element. This will be bound when the buffer manager commits bindings. + /// + /// Texture array where the element will be inserted + /// Buffer texture + /// Physical ranges of memory where the buffer texture data is located + /// Binding info for the buffer texture + /// Index of the binding on the array + /// Format of the buffer texture + public void SetBufferTextureStorage( + ITextureArray array, + ITexture texture, + MultiRange range, + TextureBindingInfo bindingInfo, + int index, + Format format) + { + _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range); + + _bufferTextureArrays.Add(new BufferTextureArrayBinding(array, texture, range, bindingInfo, index, format)); + } + + /// + /// Sets the buffer storage of a buffer image array element. This will be bound when the buffer manager commits bindings. + /// + /// Image array where the element will be inserted + /// Buffer texture + /// Physical ranges of memory where the buffer texture data is located + /// Binding info for the buffer texture + /// Index of the binding on the array + /// Format of the buffer texture + public void SetBufferTextureStorage( + IImageArray array, + ITexture texture, + MultiRange range, + TextureBindingInfo bindingInfo, + int index, + Format format) + { + _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range); + + _bufferImageArrays.Add(new BufferTextureArrayBinding(array, texture, range, bindingInfo, index, format)); + } + /// /// Force all bound textures and images to be rebound the next time CommitBindings is called. /// diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureArrayBinding.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureArrayBinding.cs new file mode 100644 index 0000000000..fa79e4f92d --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureArrayBinding.cs @@ -0,0 +1,66 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Memory.Range; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// A buffer binding to apply to a buffer texture array element. + /// + readonly struct BufferTextureArrayBinding + { + /// + /// Backend texture or image array. + /// + public T Array { get; } + + /// + /// The buffer texture. + /// + public ITexture Texture { get; } + + /// + /// Physical ranges of memory where the buffer texture data is located. + /// + public MultiRange Range { get; } + + /// + /// The image or sampler binding info for the buffer texture. + /// + public TextureBindingInfo BindingInfo { get; } + + /// + /// Index of the binding on the array. + /// + public int Index { get; } + + /// + /// The image format for the binding. + /// + public Format Format { get; } + + /// + /// Create a new buffer texture binding. + /// + /// Buffer texture + /// Physical ranges of memory where the buffer texture data is located + /// Binding info + /// Index of the binding on the array + /// Binding format + public BufferTextureArrayBinding( + T array, + ITexture texture, + MultiRange range, + TextureBindingInfo bindingInfo, + int index, + Format format) + { + Array = array; + Texture = texture; + Range = range; + BindingInfo = bindingInfo; + Index = index; + Format = format; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs index 4e1cb4e12c..6e36753e85 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs @@ -17,6 +17,8 @@ namespace Ryujinx.Graphics.Gpu.Shader public BufferDescriptor[][] ConstantBufferBindings { get; } public BufferDescriptor[][] StorageBufferBindings { get; } + public int[] TextureCounts { get; } + public int MaxTextureBinding { get; } public int MaxImageBinding { get; } @@ -34,6 +36,8 @@ namespace Ryujinx.Graphics.Gpu.Shader ConstantBufferBindings = new BufferDescriptor[stageCount][]; StorageBufferBindings = new BufferDescriptor[stageCount][]; + TextureCounts = new int[stageCount]; + int maxTextureBinding = -1; int maxImageBinding = -1; int offset = isCompute ? 0 : 1; @@ -59,13 +63,19 @@ namespace Ryujinx.Graphics.Gpu.Shader var result = new TextureBindingInfo( target, descriptor.Binding, + descriptor.ArrayLength, descriptor.CbufSlot, descriptor.HandleIndex, descriptor.Flags); - if (descriptor.Binding > maxTextureBinding) + if (descriptor.ArrayLength <= 1) { - maxTextureBinding = descriptor.Binding; + if (descriptor.Binding > maxTextureBinding) + { + maxTextureBinding = descriptor.Binding; + } + + TextureCounts[i]++; } return result; @@ -80,11 +90,12 @@ namespace Ryujinx.Graphics.Gpu.Shader target, format, descriptor.Binding, + descriptor.ArrayLength, descriptor.CbufSlot, descriptor.HandleIndex, descriptor.Flags); - if (descriptor.Binding > maxImageBinding) + if (descriptor.ArrayLength <= 1 && descriptor.Binding > maxImageBinding) { maxImageBinding = descriptor.Binding; } diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs index de6432bc10..681838a9b2 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs @@ -27,6 +27,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache /// The constant buffer 1 data of the shader /// Shader specialization state of the cached shader /// Shader specialization state of the recompiled shader + /// Resource counts shared across all shader stages /// Shader stage index public DiskCacheGpuAccessor( GpuContext context, @@ -108,6 +109,27 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache return _oldSpecState.GraphicsState.HasConstantBufferDrawParameters; } + /// + public SamplerType QuerySamplerType(int handle, int cbufSlot) + { + _newSpecState.RecordTextureSamplerType(_stageIndex, handle, cbufSlot); + return _oldSpecState.GetTextureTarget(_stageIndex, handle, cbufSlot).ConvertSamplerType(); + } + + /// + public int QueryTextureArrayLengthFromBuffer(int slot) + { + if (!_oldSpecState.TextureArrayFromBufferRegistered(_stageIndex, 0, slot)) + { + throw new DiskCacheLoadException(DiskCacheLoadResult.MissingTextureArrayLength); + } + + int arrayLength = _oldSpecState.GetTextureArrayFromBufferLength(_stageIndex, 0, slot); + _newSpecState.RegisterTextureArrayLengthFromBuffer(_stageIndex, 0, slot, arrayLength); + + return arrayLength; + } + /// public TextureFormat QueryTextureFormat(int handle, int cbufSlot) { @@ -116,13 +138,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache return ConvertToTextureFormat(format, formatSrgb); } - /// - public SamplerType QuerySamplerType(int handle, int cbufSlot) - { - _newSpecState.RecordTextureSamplerType(_stageIndex, handle, cbufSlot); - return _oldSpecState.GetTextureTarget(_stageIndex, handle, cbufSlot).ConvertSamplerType(); - } - /// public bool QueryTextureCoordNormalized(int handle, int cbufSlot) { diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs index 5036186ba1..b6a277a2af 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs @@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMinor = 2; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; - private const uint CodeGenVersion = 6462; + private const uint CodeGenVersion = 6489; private const string SharedTocFileName = "shared.toc"; private const string SharedDataFileName = "shared.data"; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadResult.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadResult.cs index ba23f70eed..d5abb9e557 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadResult.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadResult.cs @@ -20,6 +20,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache /// InvalidCb1DataLength, + /// + /// The cache is missing the length of a texture array used by the shader. + /// + MissingTextureArrayLength, + /// /// The cache is missing the descriptor of a texture used by the shader. /// @@ -60,6 +65,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache DiskCacheLoadResult.Success => "No error.", DiskCacheLoadResult.NoAccess => "Could not access the cache file.", DiskCacheLoadResult.InvalidCb1DataLength => "Constant buffer 1 data length is too low.", + DiskCacheLoadResult.MissingTextureArrayLength => "Texture array length missing from the cache file.", DiskCacheLoadResult.MissingTextureDescriptor => "Texture descriptor missing from the cache file.", DiskCacheLoadResult.FileCorruptedGeneric => "The cache file is corrupted.", DiskCacheLoadResult.FileCorruptedInvalidMagic => "Magic check failed, the cache file is corrupted.", diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs index 95763f31dc..1d22ab9332 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs @@ -72,6 +72,7 @@ namespace Ryujinx.Graphics.Gpu.Shader public ReadOnlySpan GetCode(ulong address, int minimumSize) { int size = Math.Max(minimumSize, 0x1000 - (int)(address & 0xfff)); + return MemoryMarshal.Cast(_channel.MemoryManager.GetSpan(address, size)); } @@ -119,6 +120,27 @@ namespace Ryujinx.Graphics.Gpu.Shader return _state.GraphicsState.HasUnalignedStorageBuffer || _state.ComputeState.HasUnalignedStorageBuffer; } + /// + public SamplerType QuerySamplerType(int handle, int cbufSlot) + { + _state.SpecializationState?.RecordTextureSamplerType(_stageIndex, handle, cbufSlot); + return GetTextureDescriptor(handle, cbufSlot).UnpackTextureTarget().ConvertSamplerType(); + } + + /// + public int QueryTextureArrayLengthFromBuffer(int slot) + { + int size = _compute + ? _channel.BufferManager.GetComputeUniformBufferSize(slot) + : _channel.BufferManager.GetGraphicsUniformBufferSize(_stageIndex, slot); + + int arrayLength = size / Constants.TextureHandleSizeInBytes; + + _state.SpecializationState?.RegisterTextureArrayLengthFromBuffer(_stageIndex, 0, slot, arrayLength); + + return arrayLength; + } + //// public TextureFormat QueryTextureFormat(int handle, int cbufSlot) { @@ -127,13 +149,6 @@ namespace Ryujinx.Graphics.Gpu.Shader return ConvertToTextureFormat(descriptor.UnpackFormat(), descriptor.UnpackSrgb()); } - /// - public SamplerType QuerySamplerType(int handle, int cbufSlot) - { - _state.SpecializationState?.RecordTextureSamplerType(_stageIndex, handle, cbufSlot); - return GetTextureDescriptor(handle, cbufSlot).UnpackTextureTarget().ConvertSamplerType(); - } - /// public bool QueryTextureCoordNormalized(int handle, int cbufSlot) { diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs index a5b31363b3..06e5edf1eb 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs @@ -20,6 +20,9 @@ namespace Ryujinx.Graphics.Gpu.Shader private int _reservedTextures; private int _reservedImages; + private int _staticTexturesCount; + private int _staticImagesCount; + /// /// Creates a new GPU accessor. /// @@ -48,7 +51,7 @@ namespace Ryujinx.Graphics.Gpu.Shader _reservedImages = rrc.ReservedImages; } - public int QueryBindingConstantBuffer(int index) + public int CreateConstantBufferBinding(int index) { int binding; @@ -64,7 +67,39 @@ namespace Ryujinx.Graphics.Gpu.Shader return binding + _reservedConstantBuffers; } - public int QueryBindingStorageBuffer(int index) + public int CreateImageBinding(int count, bool isBuffer) + { + int binding; + + if (_context.Capabilities.Api == TargetApi.Vulkan) + { + if (count == 1) + { + int index = _staticImagesCount++; + + if (isBuffer) + { + index += (int)_context.Capabilities.MaximumImagesPerStage; + } + + binding = GetBindingFromIndex(index, _context.Capabilities.MaximumImagesPerStage * 2, "Image"); + } + else + { + binding = (int)GetDynamicBaseIndexDual(_context.Capabilities.MaximumImagesPerStage) + _resourceCounts.ImagesCount++; + } + } + else + { + binding = _resourceCounts.ImagesCount; + + _resourceCounts.ImagesCount += count; + } + + return binding + _reservedImages; + } + + public int CreateStorageBufferBinding(int index) { int binding; @@ -80,48 +115,38 @@ namespace Ryujinx.Graphics.Gpu.Shader return binding + _reservedStorageBuffers; } - public int QueryBindingTexture(int index, bool isBuffer) + public int CreateTextureBinding(int count, bool isBuffer) { int binding; if (_context.Capabilities.Api == TargetApi.Vulkan) { - if (isBuffer) + if (count == 1) { - index += (int)_context.Capabilities.MaximumTexturesPerStage; - } + int index = _staticTexturesCount++; - binding = GetBindingFromIndex(index, _context.Capabilities.MaximumTexturesPerStage * 2, "Texture"); + if (isBuffer) + { + index += (int)_context.Capabilities.MaximumTexturesPerStage; + } + + binding = GetBindingFromIndex(index, _context.Capabilities.MaximumTexturesPerStage * 2, "Texture"); + } + else + { + binding = (int)GetDynamicBaseIndexDual(_context.Capabilities.MaximumTexturesPerStage) + _resourceCounts.TexturesCount++; + } } else { - binding = _resourceCounts.TexturesCount++; + binding = _resourceCounts.TexturesCount; + + _resourceCounts.TexturesCount += count; } return binding + _reservedTextures; } - public int QueryBindingImage(int index, bool isBuffer) - { - int binding; - - if (_context.Capabilities.Api == TargetApi.Vulkan) - { - if (isBuffer) - { - index += (int)_context.Capabilities.MaximumImagesPerStage; - } - - binding = GetBindingFromIndex(index, _context.Capabilities.MaximumImagesPerStage * 2, "Image"); - } - else - { - binding = _resourceCounts.ImagesCount++; - } - - return binding + _reservedImages; - } - private int GetBindingFromIndex(int index, uint maxPerStage, string resourceName) { if ((uint)index >= maxPerStage) @@ -148,6 +173,16 @@ namespace Ryujinx.Graphics.Gpu.Shader }; } + private static uint GetDynamicBaseIndexDual(uint maxPerStage) + { + return GetDynamicBaseIndex(maxPerStage) * 2; + } + + private static uint GetDynamicBaseIndex(uint maxPerStage) + { + return maxPerStage * Constants.ShaderStages; + } + public int QueryHostGatherBiasPrecision() => _context.Capabilities.GatherBiasPrecision; public bool QueryHostReducedPrecision() => _context.Capabilities.ReduceShaderPrecision; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs index c2258026c0..ea8f164f10 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs @@ -132,6 +132,9 @@ namespace Ryujinx.Graphics.Gpu.Shader AddDualDescriptor(stages, ResourceType.TextureAndSampler, ResourceType.BufferTexture, TextureSetIndex, textureBinding, texturesPerStage); AddDualDescriptor(stages, ResourceType.Image, ResourceType.BufferImage, ImageSetIndex, imageBinding, imagesPerStage); + AddArrayDescriptors(info.Textures, stages, TextureSetIndex, isImage: false); + AddArrayDescriptors(info.Images, stages, TextureSetIndex, isImage: true); + AddUsage(info.CBuffers, stages, UniformSetIndex, isStorage: false); AddUsage(info.SBuffers, stages, StorageSetIndex, isStorage: true); AddUsage(info.Textures, stages, TextureSetIndex, isImage: false); @@ -169,6 +172,30 @@ namespace Ryujinx.Graphics.Gpu.Shader AddDescriptor(stages, type2, setIndex, binding + count, count); } + /// + /// Adds all array descriptors (those with an array length greater than one). + /// + /// Textures to be added + /// Stages where the textures are used + /// Descriptor set index where the textures will be bound + /// True for images, false for textures + private void AddArrayDescriptors(IEnumerable textures, ResourceStages stages, int setIndex, bool isImage) + { + foreach (TextureDescriptor texture in textures) + { + if (texture.ArrayLength > 1) + { + bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer; + + ResourceType type = isBuffer + ? (isImage ? ResourceType.BufferImage : ResourceType.BufferTexture) + : (isImage ? ResourceType.Image : ResourceType.TextureAndSampler); + + _resourceDescriptors[setIndex].Add(new ResourceDescriptor(texture.Binding, texture.ArrayLength, type, stages)); + } + } + } + /// /// Adds buffer usage information to the list of usages. /// @@ -181,7 +208,7 @@ namespace Ryujinx.Graphics.Gpu.Shader { for (int index = 0; index < count; index++) { - _resourceUsages[setIndex].Add(new ResourceUsage(binding + index, type, stages)); + _resourceUsages[setIndex].Add(new ResourceUsage(binding + index, 1, type, stages)); } } @@ -198,6 +225,7 @@ namespace Ryujinx.Graphics.Gpu.Shader { _resourceUsages[setIndex].Add(new ResourceUsage( buffer.Binding, + 1, isStorage ? ResourceType.StorageBuffer : ResourceType.UniformBuffer, stages)); } @@ -220,10 +248,7 @@ namespace Ryujinx.Graphics.Gpu.Shader ? (isImage ? ResourceType.BufferImage : ResourceType.BufferTexture) : (isImage ? ResourceType.Image : ResourceType.TextureAndSampler); - _resourceUsages[setIndex].Add(new ResourceUsage( - texture.Binding, - type, - stages)); + _resourceUsages[setIndex].Add(new ResourceUsage(texture.Binding, texture.ArrayLength, type, stages)); } } diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs index 1477b7382c..c90a0b8f4b 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs @@ -30,6 +30,7 @@ namespace Ryujinx.Graphics.Gpu.Shader { PrimitiveTopology = 1 << 1, TransformFeedback = 1 << 3, + TextureArrayFromBuffer = 1 << 4, } private QueriedStateFlags _queriedState; @@ -153,6 +154,7 @@ namespace Ryujinx.Graphics.Gpu.Shader } private readonly Dictionary> _textureSpecialization; + private readonly Dictionary _textureArraySpecialization; private KeyValuePair>[] _allTextures; private Box[][] _textureByBinding; private Box[][] _imageByBinding; @@ -163,6 +165,7 @@ namespace Ryujinx.Graphics.Gpu.Shader private ShaderSpecializationState() { _textureSpecialization = new Dictionary>(); + _textureArraySpecialization = new Dictionary(); } /// @@ -323,6 +326,19 @@ namespace Ryujinx.Graphics.Gpu.Shader state.Value.CoordNormalized = coordNormalized; } + /// + /// Indicates that the coordinate normalization state of a given texture was used during the shader translation process. + /// + /// Shader stage where the texture is used + /// Offset in words of the texture handle on the texture buffer + /// Slot of the texture buffer constant buffer + /// Number of elements in the texture array + public void RegisterTextureArrayLengthFromBuffer(int stageIndex, int handle, int cbufSlot, int length) + { + _textureArraySpecialization[new TextureKey(stageIndex, handle, cbufSlot)] = length; + _queriedState |= QueriedStateFlags.TextureArrayFromBuffer; + } + /// /// Indicates that the format of a given texture was used during the shader translation process. /// @@ -379,6 +395,17 @@ namespace Ryujinx.Graphics.Gpu.Shader return GetTextureSpecState(stageIndex, handle, cbufSlot) != null; } + /// + /// Checks if a given texture array (from constant buffer) was registerd on this specialization state. + /// + /// Shader stage where the texture is used + /// Offset in words of the texture handle on the texture buffer + /// Slot of the texture buffer constant buffer + public bool TextureArrayFromBufferRegistered(int stageIndex, int handle, int cbufSlot) + { + return _textureArraySpecialization.ContainsKey(new TextureKey(stageIndex, handle, cbufSlot)); + } + /// /// Gets the recorded format of a given texture. /// @@ -413,6 +440,17 @@ namespace Ryujinx.Graphics.Gpu.Shader return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.CoordNormalized; } + /// + /// Gets the recorded length of a given texture array (from constant buffer). + /// + /// Shader stage where the texture is used + /// Offset in words of the texture handle on the texture buffer + /// Slot of the texture buffer constant buffer + public int GetTextureArrayFromBufferLength(int stageIndex, int handle, int cbufSlot) + { + return _textureArraySpecialization[new TextureKey(stageIndex, handle, cbufSlot)]; + } + /// /// Gets texture specialization state for a given texture, or create a new one if not present. /// @@ -548,6 +586,12 @@ namespace Ryujinx.Graphics.Gpu.Shader return Matches(channel, ref poolState, checkTextures, isCompute: false); } + /// + /// Converts special vertex attribute groups to their generic equivalents, for comparison purposes. + /// + /// GPU channel + /// Vertex attribute type + /// Filtered attribute private static AttributeType FilterAttributeType(GpuChannel channel, AttributeType type) { type &= ~(AttributeType.Packed | AttributeType.PackedRgb10A2Signed); @@ -838,6 +882,22 @@ namespace Ryujinx.Graphics.Gpu.Shader specState._textureSpecialization[textureKey] = textureState; } + if (specState._queriedState.HasFlag(QueriedStateFlags.TextureArrayFromBuffer)) + { + dataReader.Read(ref count); + + for (int index = 0; index < count; index++) + { + TextureKey textureKey = default; + int length = 0; + + dataReader.ReadWithMagicAndSize(ref textureKey, TexkMagic); + dataReader.Read(ref length); + + specState._textureArraySpecialization[textureKey] = length; + } + } + return specState; } @@ -902,6 +962,21 @@ namespace Ryujinx.Graphics.Gpu.Shader dataWriter.WriteWithMagicAndSize(ref textureKey, TexkMagic); dataWriter.WriteWithMagicAndSize(ref textureState.Value, TexsMagic); } + + if (_queriedState.HasFlag(QueriedStateFlags.TextureArrayFromBuffer)) + { + count = (ushort)_textureArraySpecialization.Count; + dataWriter.Write(ref count); + + foreach (var kv in _textureArraySpecialization) + { + var textureKey = kv.Key; + var length = kv.Value; + + dataWriter.WriteWithMagicAndSize(ref textureKey, TexkMagic); + dataWriter.Write(ref length); + } + } } } } diff --git a/src/Ryujinx.Graphics.OpenGL/Image/ImageArray.cs b/src/Ryujinx.Graphics.OpenGL/Image/ImageArray.cs new file mode 100644 index 0000000000..1c5acedf3a --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Image/ImageArray.cs @@ -0,0 +1,67 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.OpenGL.Image +{ + class ImageArray : IImageArray + { + private record struct TextureRef + { + public int Handle; + public Format Format; + } + + private readonly TextureRef[] _images; + + public ImageArray(int size) + { + _images = new TextureRef[size]; + } + + public void SetFormats(int index, GAL.Format[] imageFormats) + { + for (int i = 0; i < imageFormats.Length; i++) + { + _images[index + i].Format = imageFormats[i]; + } + } + + public void SetImages(int index, ITexture[] images) + { + for (int i = 0; i < images.Length; i++) + { + ITexture image = images[i]; + + if (image is TextureBase imageBase) + { + _images[index + i].Handle = imageBase.Handle; + } + else + { + _images[index + i].Handle = 0; + } + } + } + + public void Bind(int baseBinding) + { + for (int i = 0; i < _images.Length; i++) + { + if (_images[i].Handle == 0) + { + GL.BindImageTexture(baseBinding + i, 0, 0, true, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8); + } + else + { + SizedInternalFormat format = FormatTable.GetImageFormat(_images[i].Format); + + if (format != 0) + { + GL.BindImageTexture(baseBinding + i, _images[i].Handle, 0, true, 0, TextureAccess.ReadWrite, format); + } + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureArray.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureArray.cs new file mode 100644 index 0000000000..d70b0a0081 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureArray.cs @@ -0,0 +1,52 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.OpenGL.Image +{ + class TextureArray : ITextureArray + { + private record struct TextureRef + { + public TextureBase Texture; + public Sampler Sampler; + } + + private readonly TextureRef[] _textureRefs; + + public TextureArray(int size) + { + _textureRefs = new TextureRef[size]; + } + + public void SetSamplers(int index, ISampler[] samplers) + { + for (int i = 0; i < samplers.Length; i++) + { + _textureRefs[index + i].Sampler = samplers[i] as Sampler; + } + } + + public void SetTextures(int index, ITexture[] textures) + { + for (int i = 0; i < textures.Length; i++) + { + _textureRefs[index + i].Texture = textures[i] as TextureBase; + } + } + + public void Bind(int baseBinding) + { + for (int i = 0; i < _textureRefs.Length; i++) + { + if (_textureRefs[i].Texture != null) + { + _textureRefs[i].Texture.Bind(baseBinding + i); + _textureRefs[i].Sampler?.Bind(baseBinding + i); + } + else + { + TextureBase.ClearBinding(baseBinding + i); + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs index eabcb3c105..a945cbf202 100644 --- a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs +++ b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs @@ -90,6 +90,11 @@ namespace Ryujinx.Graphics.OpenGL throw new NotSupportedException(); } + public IImageArray CreateImageArray(int size, bool isBuffer) + { + return new ImageArray(size); + } + public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info) { return new Program(shaders, info.FragmentOutputMap); @@ -112,6 +117,11 @@ namespace Ryujinx.Graphics.OpenGL } } + public ITextureArray CreateTextureArray(int size, bool isBuffer) + { + return new TextureArray(size); + } + public void DeleteBuffer(BufferHandle buffer) { PersistentBuffers.Unmap(buffer); diff --git a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs index 0757fcd99f..6d066bb676 100644 --- a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs +++ b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs @@ -958,6 +958,11 @@ namespace Ryujinx.Graphics.OpenGL } } + public void SetImageArray(ShaderStage stage, int binding, IImageArray array) + { + (array as ImageArray).Bind(binding); + } + public void SetIndexBuffer(BufferRange buffer, IndexType type) { _elementsType = type.Convert(); @@ -1302,6 +1307,10 @@ namespace Ryujinx.Graphics.OpenGL } } + public void SetTextureArray(ShaderStage stage, int binding, ITextureArray array) + { + (array as TextureArray).Bind(binding); + } public void SetTransformFeedbackBuffers(ReadOnlySpan buffers) { diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs index 500de71f63..763487dac6 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs @@ -339,24 +339,17 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl private static void DeclareSamplers(CodeGenContext context, IEnumerable definitions) { - int arraySize = 0; - foreach (var definition in definitions) { - string indexExpr = string.Empty; + string arrayDecl = string.Empty; - if (definition.Type.HasFlag(SamplerType.Indexed)) + if (definition.ArrayLength > 1) { - if (arraySize == 0) - { - arraySize = ResourceManager.SamplerArraySize; - } - else if (--arraySize != 0) - { - continue; - } - - indexExpr = $"[{NumberFormatter.FormatInt(arraySize)}]"; + arrayDecl = $"[{NumberFormatter.FormatInt(definition.ArrayLength)}]"; + } + else if (definition.ArrayLength == 0) + { + arrayDecl = "[]"; } string samplerTypeName = definition.Type.ToGlslSamplerType(); @@ -368,30 +361,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl layout = $", set = {definition.Set}"; } - context.AppendLine($"layout (binding = {definition.Binding}{layout}) uniform {samplerTypeName} {definition.Name}{indexExpr};"); + context.AppendLine($"layout (binding = {definition.Binding}{layout}) uniform {samplerTypeName} {definition.Name}{arrayDecl};"); } } private static void DeclareImages(CodeGenContext context, IEnumerable definitions) { - int arraySize = 0; - foreach (var definition in definitions) { - string indexExpr = string.Empty; + string arrayDecl = string.Empty; - if (definition.Type.HasFlag(SamplerType.Indexed)) + if (definition.ArrayLength > 1) { - if (arraySize == 0) - { - arraySize = ResourceManager.SamplerArraySize; - } - else if (--arraySize != 0) - { - continue; - } - - indexExpr = $"[{NumberFormatter.FormatInt(arraySize)}]"; + arrayDecl = $"[{NumberFormatter.FormatInt(definition.ArrayLength)}]"; + } + else if (definition.ArrayLength == 0) + { + arrayDecl = "[]"; } string imageTypeName = definition.Type.ToGlslImageType(definition.Format.GetComponentType()); @@ -413,7 +399,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl layout = $", set = {definition.Set}{layout}"; } - context.AppendLine($"layout (binding = {definition.Binding}{layout}) uniform {imageTypeName} {definition.Name}{indexExpr};"); + context.AppendLine($"layout (binding = {definition.Binding}{layout}) uniform {imageTypeName} {definition.Name}{arrayDecl};"); } } diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs index eb0cb92db6..9e7f64b0e5 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs @@ -38,7 +38,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions AggregateType type = GetSrcVarType(operation.Inst, 0); - string srcExpr = GetSoureExpr(context, src, type); + string srcExpr = GetSourceExpr(context, src, type); string zero; if (type == AggregateType.FP64) @@ -80,7 +80,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions for (int argIndex = operation.SourcesCount - arity + 2; argIndex < operation.SourcesCount; argIndex++) { - builder.Append($", {GetSoureExpr(context, operation.GetSource(argIndex), dstType)}"); + builder.Append($", {GetSourceExpr(context, operation.GetSource(argIndex), dstType)}"); } } else @@ -94,7 +94,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions AggregateType dstType = GetSrcVarType(inst, argIndex); - builder.Append(GetSoureExpr(context, operation.GetSource(argIndex), dstType)); + builder.Append(GetSourceExpr(context, operation.GetSource(argIndex), dstType)); } } @@ -107,7 +107,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions // Return may optionally have a return value (and in this case it is unary). if (inst == Instruction.Return && operation.SourcesCount != 0) { - return $"{op} {GetSoureExpr(context, operation.GetSource(0), context.CurrentFunction.ReturnType)}"; + return $"{op} {GetSourceExpr(context, operation.GetSource(0), context.CurrentFunction.ReturnType)}"; } int arity = (int)(info.Type & InstType.ArityMask); @@ -118,7 +118,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions { IAstNode src = operation.GetSource(index); - string srcExpr = GetSoureExpr(context, src, GetSrcVarType(inst, index)); + string srcExpr = GetSourceExpr(context, src, GetSrcVarType(inst, index)); bool isLhs = arity == 2 && index == 0; diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenBallot.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenBallot.cs index 6cc7048bd7..000d7f7970 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenBallot.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenBallot.cs @@ -12,7 +12,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions { AggregateType dstType = GetSrcVarType(operation.Inst, 0); - string arg = GetSoureExpr(context, operation.GetSource(0), dstType); + string arg = GetSourceExpr(context, operation.GetSource(0), dstType); char component = "xyzw"[operation.Index]; if (context.HostCapabilities.SupportsShaderBallot) diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenCall.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenCall.cs index 0618ba8a38..d5448856d4 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenCall.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenCall.cs @@ -20,7 +20,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions for (int i = 0; i < args.Length; i++) { - args[i] = GetSoureExpr(context, operation.GetSource(i + 1), function.GetArgumentType(i)); + args[i] = GetSourceExpr(context, operation.GetSource(i + 1), function.GetArgumentType(i)); } return $"{function.Name}({string.Join(", ", args)})"; diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs index 5c2d16f4cc..4b28f3878d 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs @@ -140,7 +140,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions return _infoTable[(int)(inst & Instruction.Mask)]; } - public static string GetSoureExpr(CodeGenContext context, IAstNode node, AggregateType dstType) + public static string GetSourceExpr(CodeGenContext context, IAstNode node, AggregateType dstType) { return ReinterpretCast(context, node, OperandManager.GetNodeDestType(context, node), dstType); } diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs index 2e90bd16d0..b4773b819c 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs @@ -14,35 +14,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions { AstTextureOperation texOp = (AstTextureOperation)operation; - bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; - - // TODO: Bindless texture support. For now we just return 0/do nothing. - if (isBindless) - { - switch (texOp.Inst) - { - case Instruction.ImageStore: - return "// imageStore(bindless)"; - case Instruction.ImageLoad: - AggregateType componentType = texOp.Format.GetComponentType(); - - NumberFormatter.TryFormat(0, componentType, out string imageConst); - - AggregateType outputType = texOp.GetVectorType(componentType); - - if ((outputType & AggregateType.ElementCountMask) != 0) - { - return $"{Declarations.GetVarTypeName(context, outputType, precise: false)}({imageConst})"; - } - - return imageConst; - default: - return NumberFormatter.FormatInt(0); - } - } - bool isArray = (texOp.Type & SamplerType.Array) != 0; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; var texCallBuilder = new StringBuilder(); @@ -70,21 +42,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions texCallBuilder.Append(texOp.Inst == Instruction.ImageLoad ? "imageLoad" : "imageStore"); } - int srcIndex = isBindless ? 1 : 0; + int srcIndex = 0; string Src(AggregateType type) { - return GetSoureExpr(context, texOp.GetSource(srcIndex++), type); + return GetSourceExpr(context, texOp.GetSource(srcIndex++), type); } - string indexExpr = null; - - if (isIndexed) - { - indexExpr = Src(AggregateType.S32); - } - - string imageName = GetImageName(context.Properties, texOp, indexExpr); + string imageName = GetImageName(context, texOp, ref srcIndex); texCallBuilder.Append('('); texCallBuilder.Append(imageName); @@ -198,27 +163,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions AstTextureOperation texOp = (AstTextureOperation)operation; int coordsCount = texOp.Type.GetDimensions(); + int coordsIndex = 0; - bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; - - // TODO: Bindless texture support. For now we just return 0. - if (isBindless) - { - return NumberFormatter.FormatFloat(0); - } - - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - - string indexExpr = null; - - if (isIndexed) - { - indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32); - } - - string samplerName = GetSamplerName(context.Properties, texOp, indexExpr); - - int coordsIndex = isBindless || isIndexed ? 1 : 0; + string samplerName = GetSamplerName(context, texOp, ref coordsIndex); string coordsExpr; @@ -228,14 +175,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions for (int index = 0; index < coordsCount; index++) { - elems[index] = GetSoureExpr(context, texOp.GetSource(coordsIndex + index), AggregateType.FP32); + elems[index] = GetSourceExpr(context, texOp.GetSource(coordsIndex + index), AggregateType.FP32); } coordsExpr = "vec" + coordsCount + "(" + string.Join(", ", elems) + ")"; } else { - coordsExpr = GetSoureExpr(context, texOp.GetSource(coordsIndex), AggregateType.FP32); + coordsExpr = GetSourceExpr(context, texOp.GetSource(coordsIndex), AggregateType.FP32); } return $"textureQueryLod({samplerName}, {coordsExpr}){GetMask(texOp.Index)}"; @@ -250,7 +197,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions { AstTextureOperation texOp = (AstTextureOperation)operation; - bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; bool isGather = (texOp.Flags & TextureFlags.Gather) != 0; bool hasDerivatives = (texOp.Flags & TextureFlags.Derivatives) != 0; bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; @@ -260,12 +206,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0; bool isArray = (texOp.Type & SamplerType.Array) != 0; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0; bool isShadow = (texOp.Type & SamplerType.Shadow) != 0; - bool colorIsVector = isGather || !isShadow; - SamplerType type = texOp.Type & SamplerType.Mask; bool is2D = type == SamplerType.Texture2D; @@ -286,24 +229,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions hasLodLevel = false; } - // TODO: Bindless texture support. For now we just return 0. - if (isBindless) - { - string scalarValue = NumberFormatter.FormatFloat(0); - - if (colorIsVector) - { - AggregateType outputType = texOp.GetVectorType(AggregateType.FP32); - - if ((outputType & AggregateType.ElementCountMask) != 0) - { - return $"{Declarations.GetVarTypeName(context, outputType, precise: false)}({scalarValue})"; - } - } - - return scalarValue; - } - string texCall = intCoords ? "texelFetch" : "texture"; if (isGather) @@ -328,21 +253,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions texCall += "Offsets"; } - int srcIndex = isBindless ? 1 : 0; + int srcIndex = 0; string Src(AggregateType type) { - return GetSoureExpr(context, texOp.GetSource(srcIndex++), type); + return GetSourceExpr(context, texOp.GetSource(srcIndex++), type); } - string indexExpr = null; - - if (isIndexed) - { - indexExpr = Src(AggregateType.S32); - } - - string samplerName = GetSamplerName(context.Properties, texOp, indexExpr); + string samplerName = GetSamplerName(context, texOp, ref srcIndex); texCall += "(" + samplerName; @@ -512,6 +430,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions Append(Src(AggregateType.S32)); } + bool colorIsVector = isGather || !isShadow; + texCall += ")" + (colorIsVector ? GetMaskMultiDest(texOp.Index) : ""); return texCall; @@ -521,24 +441,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions { AstTextureOperation texOp = (AstTextureOperation)operation; - bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + int srcIndex = 0; - // TODO: Bindless texture support. For now we just return 0. - if (isBindless) - { - return NumberFormatter.FormatInt(0); - } - - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - - string indexExpr = null; - - if (isIndexed) - { - indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32); - } - - string samplerName = GetSamplerName(context.Properties, texOp, indexExpr); + string samplerName = GetSamplerName(context, texOp, ref srcIndex); return $"textureSamples({samplerName})"; } @@ -547,24 +452,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions { AstTextureOperation texOp = (AstTextureOperation)operation; - bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + int srcIndex = 0; - // TODO: Bindless texture support. For now we just return 0. - if (isBindless) - { - return NumberFormatter.FormatInt(0); - } - - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - - string indexExpr = null; - - if (isIndexed) - { - indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32); - } - - string samplerName = GetSamplerName(context.Properties, texOp, indexExpr); + string samplerName = GetSamplerName(context, texOp, ref srcIndex); if (texOp.Index == 3) { @@ -578,9 +468,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions if (hasLod) { - int lodSrcIndex = isBindless || isIndexed ? 1 : 0; - IAstNode lod = operation.GetSource(lodSrcIndex); - string lodExpr = GetSoureExpr(context, lod, GetSrcVarType(operation.Inst, lodSrcIndex)); + IAstNode lod = operation.GetSource(srcIndex); + string lodExpr = GetSourceExpr(context, lod, GetSrcVarType(operation.Inst, srcIndex)); texCall = $"textureSize({samplerName}, {lodExpr}){GetMask(texOp.Index)}"; } @@ -697,12 +586,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions if (storageKind == StorageKind.Input) { - string expr = GetSoureExpr(context, operation.GetSource(srcIndex++), AggregateType.S32); + string expr = GetSourceExpr(context, operation.GetSource(srcIndex++), AggregateType.S32); varName = $"gl_in[{expr}].{varName}"; } else if (storageKind == StorageKind.Output) { - string expr = GetSoureExpr(context, operation.GetSource(srcIndex++), AggregateType.S32); + string expr = GetSourceExpr(context, operation.GetSource(srcIndex++), AggregateType.S32); varName = $"gl_out[{expr}].{varName}"; } } @@ -735,38 +624,40 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions } else { - varName += $"[{GetSoureExpr(context, src, AggregateType.S32)}]"; + varName += $"[{GetSourceExpr(context, src, AggregateType.S32)}]"; } } if (isStore) { varType &= AggregateType.ElementTypeMask; - varName = $"{varName} = {GetSoureExpr(context, operation.GetSource(srcIndex), varType)}"; + varName = $"{varName} = {GetSourceExpr(context, operation.GetSource(srcIndex), varType)}"; } return varName; } - private static string GetSamplerName(ShaderProperties resourceDefinitions, AstTextureOperation texOp, string indexExpr) + private static string GetSamplerName(CodeGenContext context, AstTextureOperation texOp, ref int srcIndex) { - string name = resourceDefinitions.Textures[texOp.Binding].Name; + TextureDefinition definition = context.Properties.Textures[texOp.Binding]; + string name = definition.Name; - if (texOp.Type.HasFlag(SamplerType.Indexed)) + if (definition.ArrayLength != 1) { - name = $"{name}[{indexExpr}]"; + name = $"{name}[{GetSourceExpr(context, texOp.GetSource(srcIndex++), AggregateType.S32)}]"; } return name; } - private static string GetImageName(ShaderProperties resourceDefinitions, AstTextureOperation texOp, string indexExpr) + private static string GetImageName(CodeGenContext context, AstTextureOperation texOp, ref int srcIndex) { - string name = resourceDefinitions.Images[texOp.Binding].Name; + TextureDefinition definition = context.Properties.Images[texOp.Binding]; + string name = definition.Name; - if (texOp.Type.HasFlag(SamplerType.Indexed)) + if (definition.ArrayLength != 1) { - name = $"{name}[{indexExpr}]"; + name = $"{name}[{GetSourceExpr(context, texOp.GetSource(srcIndex++), AggregateType.S32)}]"; } return name; diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs index ad84c48502..4469785d2a 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs @@ -13,8 +13,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions IAstNode src0 = operation.GetSource(0); IAstNode src1 = operation.GetSource(1); - string src0Expr = GetSoureExpr(context, src0, GetSrcVarType(operation.Inst, 0)); - string src1Expr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 1)); + string src0Expr = GetSourceExpr(context, src0, GetSrcVarType(operation.Inst, 0)); + string src1Expr = GetSourceExpr(context, src1, GetSrcVarType(operation.Inst, 1)); return $"packDouble2x32(uvec2({src0Expr}, {src1Expr}))"; } @@ -24,8 +24,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions IAstNode src0 = operation.GetSource(0); IAstNode src1 = operation.GetSource(1); - string src0Expr = GetSoureExpr(context, src0, GetSrcVarType(operation.Inst, 0)); - string src1Expr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 1)); + string src0Expr = GetSourceExpr(context, src0, GetSrcVarType(operation.Inst, 0)); + string src1Expr = GetSourceExpr(context, src1, GetSrcVarType(operation.Inst, 1)); return $"packHalf2x16(vec2({src0Expr}, {src1Expr}))"; } @@ -34,7 +34,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions { IAstNode src = operation.GetSource(0); - string srcExpr = GetSoureExpr(context, src, GetSrcVarType(operation.Inst, 0)); + string srcExpr = GetSourceExpr(context, src, GetSrcVarType(operation.Inst, 0)); return $"unpackDouble2x32({srcExpr}){GetMask(operation.Index)}"; } @@ -43,7 +43,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions { IAstNode src = operation.GetSource(0); - string srcExpr = GetSoureExpr(context, src, GetSrcVarType(operation.Inst, 0)); + string srcExpr = GetSourceExpr(context, src, GetSrcVarType(operation.Inst, 0)); return $"unpackHalf2x16({srcExpr}){GetMask(operation.Index)}"; } diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenShuffle.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenShuffle.cs index 6d3859efdc..b72b94d90a 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenShuffle.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenShuffle.cs @@ -9,8 +9,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions { public static string Shuffle(CodeGenContext context, AstOperation operation) { - string value = GetSoureExpr(context, operation.GetSource(0), AggregateType.FP32); - string index = GetSoureExpr(context, operation.GetSource(1), AggregateType.U32); + string value = GetSourceExpr(context, operation.GetSource(0), AggregateType.FP32); + string index = GetSourceExpr(context, operation.GetSource(1), AggregateType.U32); if (context.HostCapabilities.SupportsShaderBallot) { diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenVector.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenVector.cs index 70174a5ba9..a300c7750c 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenVector.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenVector.cs @@ -13,7 +13,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions IAstNode vector = operation.GetSource(0); IAstNode index = operation.GetSource(1); - string vectorExpr = GetSoureExpr(context, vector, OperandManager.GetNodeDestType(context, vector)); + string vectorExpr = GetSourceExpr(context, vector, OperandManager.GetNodeDestType(context, vector)); if (index is AstOperand indexOperand && indexOperand.Type == OperandType.Constant) { @@ -23,7 +23,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions } else { - string indexExpr = GetSoureExpr(context, index, GetSrcVarType(operation.Inst, 1)); + string indexExpr = GetSourceExpr(context, index, GetSrcVarType(operation.Inst, 1)); return $"{vectorExpr}[{indexExpr}]"; } diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs index 53ecc4531a..a350b089c5 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs @@ -146,9 +146,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl } else if (operation is AstTextureOperation texOp) { - if (texOp.Inst == Instruction.ImageLoad || - texOp.Inst == Instruction.ImageStore || - texOp.Inst == Instruction.ImageAtomic) + if (texOp.Inst.IsImage()) { return texOp.GetVectorType(texOp.Format.GetComponentType()); } diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs index 17c3eefe34..2b1fdf44c3 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs @@ -34,8 +34,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv public Dictionary SharedMemories { get; } = new(); public Dictionary SamplersTypes { get; } = new(); - public Dictionary Samplers { get; } = new(); - public Dictionary Images { get; } = new(); + public Dictionary Samplers { get; } = new(); + public Dictionary Images { get; } = new(); public Dictionary Inputs { get; } = new(); public Dictionary Outputs { get; } = new(); diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs index b748242558..9633c522ea 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs @@ -181,9 +181,27 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv var sampledImageType = context.TypeSampledImage(imageType); var sampledImagePointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageType); - var sampledImageVariable = context.Variable(sampledImagePointerType, StorageClass.UniformConstant); + var sampledImageArrayPointerType = sampledImagePointerType; - context.Samplers.Add(sampler.Binding, (imageType, sampledImageType, sampledImageVariable)); + if (sampler.ArrayLength == 0) + { + var sampledImageArrayType = context.TypeRuntimeArray(sampledImageType); + sampledImageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageArrayType); + } + else if (sampler.ArrayLength != 1) + { + var sampledImageArrayType = context.TypeArray(sampledImageType, context.Constant(context.TypeU32(), sampler.ArrayLength)); + sampledImageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageArrayType); + } + + var sampledImageVariable = context.Variable(sampledImageArrayPointerType, StorageClass.UniformConstant); + + context.Samplers.Add(sampler.Binding, new SamplerDeclaration( + imageType, + sampledImageType, + sampledImagePointerType, + sampledImageVariable, + sampler.ArrayLength != 1)); context.SamplersTypes.Add(sampler.Binding, sampler.Type); context.Name(sampledImageVariable, sampler.Name); @@ -211,9 +229,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv GetImageFormat(image.Format)); var imagePointerType = context.TypePointer(StorageClass.UniformConstant, imageType); - var imageVariable = context.Variable(imagePointerType, StorageClass.UniformConstant); + var imageArrayPointerType = imagePointerType; - context.Images.Add(image.Binding, (imageType, imageVariable)); + if (image.ArrayLength == 0) + { + var imageArrayType = context.TypeRuntimeArray(imageType); + imageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, imageArrayType); + } + else if (image.ArrayLength != 1) + { + var imageArrayType = context.TypeArray(imageType, context.Constant(context.TypeU32(), image.ArrayLength)); + imageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, imageArrayType); + } + + var imageVariable = context.Variable(imageArrayPointerType, StorageClass.UniformConstant); + + context.Images.Add(image.Binding, new ImageDeclaration(imageType, imagePointerType, imageVariable, image.ArrayLength != 1)); context.Name(imageVariable, image.Name); context.Decorate(imageVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex); diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/ImageDeclaration.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/ImageDeclaration.cs new file mode 100644 index 0000000000..1e0aee7347 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/ImageDeclaration.cs @@ -0,0 +1,20 @@ +using Spv.Generator; + +namespace Ryujinx.Graphics.Shader.CodeGen.Spirv +{ + readonly struct ImageDeclaration + { + public readonly Instruction ImageType; + public readonly Instruction ImagePointerType; + public readonly Instruction Image; + public readonly bool IsIndexed; + + public ImageDeclaration(Instruction imageType, Instruction imagePointerType, Instruction image, bool isIndexed) + { + ImageType = imageType; + ImagePointerType = imagePointerType; + Image = image; + IsIndexed = isIndexed; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs index 601753cb0d..409e466cd7 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs @@ -591,34 +591,28 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv { AstTextureOperation texOp = (AstTextureOperation)operation; - bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; - var componentType = texOp.Format.GetComponentType(); - // TODO: Bindless texture support. For now we just return 0/do nothing. - if (isBindless) - { - return new OperationResult(componentType, componentType switch - { - AggregateType.S32 => context.Constant(context.TypeS32(), 0), - AggregateType.U32 => context.Constant(context.TypeU32(), 0u), - _ => context.Constant(context.TypeFP32(), 0f), - }); - } - bool isArray = (texOp.Type & SamplerType.Array) != 0; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - int srcIndex = isBindless ? 1 : 0; + int srcIndex = 0; SpvInstruction Src(AggregateType type) { return context.Get(type, texOp.GetSource(srcIndex++)); } - if (isIndexed) + ImageDeclaration declaration = context.Images[texOp.Binding]; + SpvInstruction image = declaration.Image; + + SpvInstruction resultType = context.GetType(componentType); + SpvInstruction imagePointerType = context.TypePointer(StorageClass.Image, resultType); + + if (declaration.IsIndexed) { - Src(AggregateType.S32); + SpvInstruction textureIndex = Src(AggregateType.S32); + + image = context.AccessChain(imagePointerType, image, textureIndex); } int coordsCount = texOp.Type.GetDimensions(); @@ -646,14 +640,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv SpvInstruction value = Src(componentType); - (var imageType, var imageVariable) = context.Images[texOp.Binding]; - - context.Load(imageType, imageVariable); - - SpvInstruction resultType = context.GetType(componentType); - SpvInstruction imagePointerType = context.TypePointer(StorageClass.Image, resultType); - - var pointer = context.ImageTexelPointer(imagePointerType, imageVariable, pCoords, context.Constant(context.TypeU32(), 0)); + var pointer = context.ImageTexelPointer(imagePointerType, image, pCoords, context.Constant(context.TypeU32(), 0)); var one = context.Constant(context.TypeU32(), 1); var zero = context.Constant(context.TypeU32(), 0); @@ -683,31 +670,29 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv { AstTextureOperation texOp = (AstTextureOperation)operation; - bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; - var componentType = texOp.Format.GetComponentType(); - // TODO: Bindless texture support. For now we just return 0/do nothing. - if (isBindless) - { - return GetZeroOperationResult(context, texOp, componentType, isVector: true); - } - bool isArray = (texOp.Type & SamplerType.Array) != 0; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - int srcIndex = isBindless ? 1 : 0; + int srcIndex = 0; SpvInstruction Src(AggregateType type) { return context.Get(type, texOp.GetSource(srcIndex++)); } - if (isIndexed) + ImageDeclaration declaration = context.Images[texOp.Binding]; + SpvInstruction image = declaration.Image; + + if (declaration.IsIndexed) { - Src(AggregateType.S32); + SpvInstruction textureIndex = Src(AggregateType.S32); + + image = context.AccessChain(declaration.ImagePointerType, image, textureIndex); } + image = context.Load(declaration.ImageType, image); + int coordsCount = texOp.Type.GetDimensions(); int pCount = coordsCount + (isArray ? 1 : 0); @@ -731,9 +716,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv pCoords = Src(AggregateType.S32); } - (var imageType, var imageVariable) = context.Images[texOp.Binding]; - - var image = context.Load(imageType, imageVariable); var imageComponentType = context.GetType(componentType); var swizzledResultType = texOp.GetVectorType(componentType); @@ -747,29 +729,27 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv { AstTextureOperation texOp = (AstTextureOperation)operation; - bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; - - // TODO: Bindless texture support. For now we just return 0/do nothing. - if (isBindless) - { - return OperationResult.Invalid; - } - bool isArray = (texOp.Type & SamplerType.Array) != 0; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - int srcIndex = isBindless ? 1 : 0; + int srcIndex = 0; SpvInstruction Src(AggregateType type) { return context.Get(type, texOp.GetSource(srcIndex++)); } - if (isIndexed) + ImageDeclaration declaration = context.Images[texOp.Binding]; + SpvInstruction image = declaration.Image; + + if (declaration.IsIndexed) { - Src(AggregateType.S32); + SpvInstruction textureIndex = Src(AggregateType.S32); + + image = context.AccessChain(declaration.ImagePointerType, image, textureIndex); } + image = context.Load(declaration.ImageType, image); + int coordsCount = texOp.Type.GetDimensions(); int pCount = coordsCount + (isArray ? 1 : 0); @@ -818,10 +798,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv var texel = context.CompositeConstruct(context.TypeVector(context.GetType(componentType), ComponentsCount), cElems); - (var imageType, var imageVariable) = context.Images[texOp.Binding]; - - var image = context.Load(imageType, imageVariable); - context.ImageWrite(image, pCoords, texel, ImageOperandsMask.MaskNone); return OperationResult.Invalid; @@ -854,16 +830,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv { AstTextureOperation texOp = (AstTextureOperation)operation; - bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; - - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - - // TODO: Bindless texture support. For now we just return 0. - if (isBindless) - { - return new OperationResult(AggregateType.S32, context.Constant(context.TypeS32(), 0)); - } - int srcIndex = 0; SpvInstruction Src(AggregateType type) @@ -871,11 +837,18 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv return context.Get(type, texOp.GetSource(srcIndex++)); } - if (isIndexed) + SamplerDeclaration declaration = context.Samplers[texOp.Binding]; + SpvInstruction image = declaration.Image; + + if (declaration.IsIndexed) { - Src(AggregateType.S32); + SpvInstruction textureIndex = Src(AggregateType.S32); + + image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex); } + image = context.Load(declaration.SampledImageType, image); + int pCount = texOp.Type.GetDimensions(); SpvInstruction pCoords; @@ -897,10 +870,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv pCoords = Src(AggregateType.FP32); } - (_, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding]; - - var image = context.Load(sampledImageType, sampledImageVariable); - var resultType = context.TypeVector(context.TypeFP32(), 2); var packed = context.ImageQueryLod(resultType, image, pCoords); var result = context.CompositeExtract(context.TypeFP32(), packed, (SpvLiteralInteger)texOp.Index); @@ -1182,7 +1151,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv { AstTextureOperation texOp = (AstTextureOperation)operation; - bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; bool isGather = (texOp.Flags & TextureFlags.Gather) != 0; bool hasDerivatives = (texOp.Flags & TextureFlags.Derivatives) != 0; bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; @@ -1192,30 +1160,28 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0; bool isArray = (texOp.Type & SamplerType.Array) != 0; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0; bool isShadow = (texOp.Type & SamplerType.Shadow) != 0; - bool colorIsVector = isGather || !isShadow; - - // TODO: Bindless texture support. For now we just return 0. - if (isBindless) - { - return GetZeroOperationResult(context, texOp, AggregateType.FP32, colorIsVector); - } - - int srcIndex = isBindless ? 1 : 0; + int srcIndex = 0; SpvInstruction Src(AggregateType type) { return context.Get(type, texOp.GetSource(srcIndex++)); } - if (isIndexed) + SamplerDeclaration declaration = context.Samplers[texOp.Binding]; + SpvInstruction image = declaration.Image; + + if (declaration.IsIndexed) { - Src(AggregateType.S32); + SpvInstruction textureIndex = Src(AggregateType.S32); + + image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex); } + image = context.Load(declaration.SampledImageType, image); + int coordsCount = texOp.Type.GetDimensions(); int pCount = coordsCount; @@ -1419,15 +1385,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv operandsList.Add(sample); } + bool colorIsVector = isGather || !isShadow; + var resultType = colorIsVector ? context.TypeVector(context.TypeFP32(), 4) : context.TypeFP32(); - (var imageType, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding]; - - var image = context.Load(sampledImageType, sampledImageVariable); - if (intCoords) { - image = context.Image(imageType, image); + image = context.Image(declaration.ImageType, image); } var operands = operandsList.ToArray(); @@ -1485,25 +1449,18 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv { AstTextureOperation texOp = (AstTextureOperation)operation; - bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + SamplerDeclaration declaration = context.Samplers[texOp.Binding]; + SpvInstruction image = declaration.Image; - // TODO: Bindless texture support. For now we just return 0. - if (isBindless) + if (declaration.IsIndexed) { - return new OperationResult(AggregateType.S32, context.Constant(context.TypeS32(), 0)); + SpvInstruction textureIndex = context.GetS32(texOp.GetSource(0)); + + image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex); } - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - - if (isIndexed) - { - context.GetS32(texOp.GetSource(0)); - } - - (var imageType, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding]; - - var image = context.Load(sampledImageType, sampledImageVariable); - image = context.Image(imageType, image); + image = context.Load(declaration.SampledImageType, image); + image = context.Image(declaration.ImageType, image); SpvInstruction result = context.ImageQuerySamples(context.TypeS32(), image); @@ -1514,25 +1471,18 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv { AstTextureOperation texOp = (AstTextureOperation)operation; - bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + SamplerDeclaration declaration = context.Samplers[texOp.Binding]; + SpvInstruction image = declaration.Image; - // TODO: Bindless texture support. For now we just return 0. - if (isBindless) + if (declaration.IsIndexed) { - return new OperationResult(AggregateType.S32, context.Constant(context.TypeS32(), 0)); + SpvInstruction textureIndex = context.GetS32(texOp.GetSource(0)); + + image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex); } - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - - if (isIndexed) - { - context.GetS32(texOp.GetSource(0)); - } - - (var imageType, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding]; - - var image = context.Load(sampledImageType, sampledImageVariable); - image = context.Image(imageType, image); + image = context.Load(declaration.SampledImageType, image); + image = context.Image(declaration.ImageType, image); if (texOp.Index == 3) { @@ -1556,7 +1506,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv if (hasLod) { - int lodSrcIndex = isBindless || isIndexed ? 1 : 0; + int lodSrcIndex = declaration.IsIndexed ? 1 : 0; var lod = context.GetS32(operation.GetSource(lodSrcIndex)); result = context.ImageQuerySizeLod(resultType, image, lod); } @@ -1929,38 +1879,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv return context.Load(context.GetType(varType), context.Inputs[ioDefinition]); } - private static OperationResult GetZeroOperationResult( - CodeGenContext context, - AstTextureOperation texOp, - AggregateType scalarType, - bool isVector) - { - var zero = scalarType switch - { - AggregateType.S32 => context.Constant(context.TypeS32(), 0), - AggregateType.U32 => context.Constant(context.TypeU32(), 0u), - _ => context.Constant(context.TypeFP32(), 0f), - }; - - if (isVector) - { - AggregateType outputType = texOp.GetVectorType(scalarType); - - if ((outputType & AggregateType.ElementCountMask) != 0) - { - int componentsCount = BitOperations.PopCount((uint)texOp.Index); - - SpvInstruction[] values = new SpvInstruction[componentsCount]; - - values.AsSpan().Fill(zero); - - return new OperationResult(outputType, context.ConstantComposite(context.GetType(outputType), values)); - } - } - - return new OperationResult(scalarType, zero); - } - private static SpvInstruction GetSwizzledResult(CodeGenContext context, SpvInstruction vector, AggregateType swizzledResultType, int mask) { if ((swizzledResultType & AggregateType.ElementCountMask) != 0) diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SamplerDeclaration.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SamplerDeclaration.cs new file mode 100644 index 0000000000..9e0ecd7947 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SamplerDeclaration.cs @@ -0,0 +1,27 @@ +using Spv.Generator; + +namespace Ryujinx.Graphics.Shader.CodeGen.Spirv +{ + readonly struct SamplerDeclaration + { + public readonly Instruction ImageType; + public readonly Instruction SampledImageType; + public readonly Instruction SampledImagePointerType; + public readonly Instruction Image; + public readonly bool IsIndexed; + + public SamplerDeclaration( + Instruction imageType, + Instruction sampledImageType, + Instruction sampledImagePointerType, + Instruction image, + bool isIndexed) + { + ImageType = imageType; + SampledImageType = sampledImageType; + SampledImagePointerType = sampledImagePointerType; + Image = image; + IsIndexed = isIndexed; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs index df6d29dc59..99366ad67e 100644 --- a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs +++ b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs @@ -26,47 +26,42 @@ namespace Ryujinx.Graphics.Shader /// Span of the memory location ReadOnlySpan GetCode(ulong address, int minimumSize); + /// + /// Gets the size in bytes of a bound constant buffer for the current shader stage. + /// + /// The number of the constant buffer to get the size from + /// Size in bytes + int QueryTextureArrayLengthFromBuffer(int slot); + /// /// Queries the binding number of a constant buffer. /// /// Constant buffer index /// Binding number - int QueryBindingConstantBuffer(int index) - { - return index + 1; - } + int CreateConstantBufferBinding(int index); + + /// + /// Queries the binding number of an image. + /// + /// For array of images, the number of elements of the array, otherwise it should be 1 + /// Indicates if the image is a buffer image + /// Binding number + int CreateImageBinding(int count, bool isBuffer); /// /// Queries the binding number of a storage buffer. /// /// Storage buffer index /// Binding number - int QueryBindingStorageBuffer(int index) - { - return index; - } + int CreateStorageBufferBinding(int index); /// /// Queries the binding number of a texture. /// - /// Texture index + /// For array of textures, the number of elements of the array, otherwise it should be 1 /// Indicates if the texture is a buffer texture /// Binding number - int QueryBindingTexture(int index, bool isBuffer) - { - return index; - } - - /// - /// Queries the binding number of an image. - /// - /// Image index - /// Indicates if the image is a buffer image - /// Binding number - int QueryBindingImage(int index, bool isBuffer) - { - return index; - } + int CreateTextureBinding(int count, bool isBuffer); /// /// Queries Local Size X for compute shaders. diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs index e5695ebc2b..8703e660e0 100644 --- a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs +++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs @@ -161,5 +161,17 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation inst &= Instruction.Mask; return inst == Instruction.Lod || inst == Instruction.TextureQuerySamples || inst == Instruction.TextureQuerySize; } + + public static bool IsImage(this Instruction inst) + { + inst &= Instruction.Mask; + return inst == Instruction.ImageAtomic || inst == Instruction.ImageLoad || inst == Instruction.ImageStore; + } + + public static bool IsImageStore(this Instruction inst) + { + inst &= Instruction.Mask; + return inst == Instruction.ImageAtomic || inst == Instruction.ImageStore; + } } } diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs index f5396a884f..0c1b2a3f35 100644 --- a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs +++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs @@ -20,13 +20,13 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation } set { - if (value != null && value.Type == OperandType.LocalVariable) - { - value.AsgOp = this; - } - if (value != null) { + if (value.Type == OperandType.LocalVariable) + { + value.AsgOp = this; + } + _dests = new[] { value }; } else diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs index fa5550a647..1b82e2945b 100644 --- a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs +++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs @@ -26,9 +26,8 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation Binding = binding; } - public void TurnIntoIndexed(int binding) + public void TurnIntoArray(int binding) { - Type |= SamplerType.Indexed; Flags &= ~TextureFlags.Bindless; Binding = binding; } diff --git a/src/Ryujinx.Graphics.Shader/SamplerType.cs b/src/Ryujinx.Graphics.Shader/SamplerType.cs index 85e97368f7..66c748bf3a 100644 --- a/src/Ryujinx.Graphics.Shader/SamplerType.cs +++ b/src/Ryujinx.Graphics.Shader/SamplerType.cs @@ -16,9 +16,8 @@ namespace Ryujinx.Graphics.Shader Mask = 0xff, Array = 1 << 8, - Indexed = 1 << 9, - Multisample = 1 << 10, - Shadow = 1 << 11, + Multisample = 1 << 9, + Shadow = 1 << 10, } static class SamplerTypeExtensions @@ -36,6 +35,36 @@ namespace Ryujinx.Graphics.Shader }; } + public static string ToShortSamplerType(this SamplerType type) + { + string typeName = (type & SamplerType.Mask) switch + { + SamplerType.Texture1D => "1d", + SamplerType.TextureBuffer => "b", + SamplerType.Texture2D => "2d", + SamplerType.Texture3D => "3d", + SamplerType.TextureCube => "cube", + _ => throw new ArgumentException($"Invalid sampler type \"{type}\"."), + }; + + if ((type & SamplerType.Multisample) != 0) + { + typeName += "ms"; + } + + if ((type & SamplerType.Array) != 0) + { + typeName += "a"; + } + + if ((type & SamplerType.Shadow) != 0) + { + typeName += "s"; + } + + return typeName; + } + public static string ToGlslSamplerType(this SamplerType type) { string typeName = (type & SamplerType.Mask) switch diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/TextureDefinition.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/TextureDefinition.cs index e45c82854b..bdd3a2ed14 100644 --- a/src/Ryujinx.Graphics.Shader/StructuredIr/TextureDefinition.cs +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/TextureDefinition.cs @@ -4,15 +4,17 @@ namespace Ryujinx.Graphics.Shader { public int Set { get; } public int Binding { get; } + public int ArrayLength { get; } public string Name { get; } public SamplerType Type { get; } public TextureFormat Format { get; } public TextureUsageFlags Flags { get; } - public TextureDefinition(int set, int binding, string name, SamplerType type, TextureFormat format, TextureUsageFlags flags) + public TextureDefinition(int set, int binding, int arrayLength, string name, SamplerType type, TextureFormat format, TextureUsageFlags flags) { Set = set; Binding = binding; + ArrayLength = arrayLength; Name = name; Type = type; Format = format; @@ -21,7 +23,7 @@ namespace Ryujinx.Graphics.Shader public TextureDefinition SetFlag(TextureUsageFlags flag) { - return new TextureDefinition(Set, Binding, Name, Type, Format, Flags | flag); + return new TextureDefinition(Set, Binding, ArrayLength, Name, Type, Format, Flags | flag); } } } diff --git a/src/Ryujinx.Graphics.Shader/TextureDescriptor.cs b/src/Ryujinx.Graphics.Shader/TextureDescriptor.cs index 1130b63b81..38834da726 100644 --- a/src/Ryujinx.Graphics.Shader/TextureDescriptor.cs +++ b/src/Ryujinx.Graphics.Shader/TextureDescriptor.cs @@ -11,16 +11,25 @@ namespace Ryujinx.Graphics.Shader public readonly int CbufSlot; public readonly int HandleIndex; + public readonly int ArrayLength; public readonly TextureUsageFlags Flags; - public TextureDescriptor(int binding, SamplerType type, TextureFormat format, int cbufSlot, int handleIndex, TextureUsageFlags flags) + public TextureDescriptor( + int binding, + SamplerType type, + TextureFormat format, + int cbufSlot, + int handleIndex, + int arrayLength, + TextureUsageFlags flags) { Binding = binding; Type = type; Format = format; CbufSlot = cbufSlot; HandleIndex = handleIndex; + ArrayLength = arrayLength; Flags = flags; } } diff --git a/src/Ryujinx.Graphics.Shader/TextureHandle.cs b/src/Ryujinx.Graphics.Shader/TextureHandle.cs index fc9ab2d674..7df9c8e47b 100644 --- a/src/Ryujinx.Graphics.Shader/TextureHandle.cs +++ b/src/Ryujinx.Graphics.Shader/TextureHandle.cs @@ -88,7 +88,7 @@ namespace Ryujinx.Graphics.Shader { (int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = UnpackOffsets(wordOffset); - int handle = cachedTextureBuffer.Length != 0 ? cachedTextureBuffer[textureWordOffset] : 0; + int handle = textureWordOffset < cachedTextureBuffer.Length ? cachedTextureBuffer[textureWordOffset] : 0; // The "wordOffset" (which is really the immediate value used on texture instructions on the shader) // is a 13-bit value. However, in order to also support separate samplers and textures (which uses @@ -102,7 +102,7 @@ namespace Ryujinx.Graphics.Shader if (handleType != TextureHandleType.SeparateConstantSamplerHandle) { - samplerHandle = cachedSamplerBuffer.Length != 0 ? cachedSamplerBuffer[samplerWordOffset] : 0; + samplerHandle = samplerWordOffset < cachedSamplerBuffer.Length ? cachedSamplerBuffer[samplerWordOffset] : 0; } else { diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs index a889032743..ad955278fe 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs @@ -15,8 +15,12 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations // - The handle is a constant buffer value. // - The handle is the result of a bitwise OR logical operation. // - Both sources of the OR operation comes from a constant buffer. - for (LinkedListNode node = block.Operations.First; node != null; node = node.Next) + LinkedListNode nextNode; + + for (LinkedListNode node = block.Operations.First; node != null; node = nextNode) { + nextNode = node.Next; + if (node.Value is not TextureOperation texOp) { continue; @@ -27,185 +31,207 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations continue; } - if (texOp.Inst == Instruction.TextureSample || texOp.Inst.IsTextureQuery()) + if (!TryConvertBindless(block, resourceManager, gpuAccessor, texOp)) { - Operand bindlessHandle = texOp.GetSource(0); + // If we can't do bindless elimination, remove the texture operation. + // Set any destination variables to zero. - // In some cases the compiler uses a shuffle operation to get the handle, - // for some textureGrad implementations. In those cases, we can skip the shuffle. - if (bindlessHandle.AsgOp is Operation shuffleOp && shuffleOp.Inst == Instruction.Shuffle) + for (int destIndex = 0; destIndex < texOp.DestsCount; destIndex++) { - bindlessHandle = shuffleOp.GetSource(0); + block.Operations.AddBefore(node, new Operation(Instruction.Copy, texOp.GetDest(destIndex), OperandHelper.Const(0))); } - bindlessHandle = Utils.FindLastOperation(bindlessHandle, block); + Utils.DeleteNode(node, texOp); + } + } + } - // Some instructions do not encode an accurate sampler type: - // - Most instructions uses the same type for 1D and Buffer. - // - Query instructions may not have any type. - // For those cases, we need to try getting the type from current GPU state, - // as long bindless elimination is successful and we know where the texture descriptor is located. - bool rewriteSamplerType = - texOp.Type == SamplerType.TextureBuffer || - texOp.Inst == Instruction.TextureQuerySamples || - texOp.Inst == Instruction.TextureQuerySize; + private static bool TryConvertBindless(BasicBlock block, ResourceManager resourceManager, IGpuAccessor gpuAccessor, TextureOperation texOp) + { + if (texOp.Inst == Instruction.TextureSample || texOp.Inst.IsTextureQuery()) + { + Operand bindlessHandle = texOp.GetSource(0); - if (bindlessHandle.Type == OperandType.ConstantBuffer) + // In some cases the compiler uses a shuffle operation to get the handle, + // for some textureGrad implementations. In those cases, we can skip the shuffle. + if (bindlessHandle.AsgOp is Operation shuffleOp && shuffleOp.Inst == Instruction.Shuffle) + { + bindlessHandle = shuffleOp.GetSource(0); + } + + bindlessHandle = Utils.FindLastOperation(bindlessHandle, block); + + // Some instructions do not encode an accurate sampler type: + // - Most instructions uses the same type for 1D and Buffer. + // - Query instructions may not have any type. + // For those cases, we need to try getting the type from current GPU state, + // as long bindless elimination is successful and we know where the texture descriptor is located. + bool rewriteSamplerType = + texOp.Type == SamplerType.TextureBuffer || + texOp.Inst == Instruction.TextureQuerySamples || + texOp.Inst == Instruction.TextureQuerySize; + + if (bindlessHandle.Type == OperandType.ConstantBuffer) + { + SetHandle( + resourceManager, + gpuAccessor, + texOp, + bindlessHandle.GetCbufOffset(), + bindlessHandle.GetCbufSlot(), + rewriteSamplerType, + isImage: false); + + return true; + } + + if (!TryGetOperation(bindlessHandle.AsgOp, out Operation handleCombineOp)) + { + return false; + } + + if (handleCombineOp.Inst != Instruction.BitwiseOr) + { + return false; + } + + Operand src0 = Utils.FindLastOperation(handleCombineOp.GetSource(0), block); + Operand src1 = Utils.FindLastOperation(handleCombineOp.GetSource(1), block); + + // For cases where we have a constant, ensure that the constant is always + // the second operand. + // Since this is a commutative operation, both are fine, + // and having a "canonical" representation simplifies some checks below. + if (src0.Type == OperandType.Constant && src1.Type != OperandType.Constant) + { + (src0, src1) = (src1, src0); + } + + TextureHandleType handleType = TextureHandleType.SeparateSamplerHandle; + + // Try to match the following patterns: + // Masked pattern: + // - samplerHandle = samplerHandle & 0xFFF00000; + // - textureHandle = textureHandle & 0xFFFFF; + // - combinedHandle = samplerHandle | textureHandle; + // Where samplerHandle and textureHandle comes from a constant buffer. + // Shifted pattern: + // - samplerHandle = samplerId << 20; + // - combinedHandle = samplerHandle | textureHandle; + // Where samplerId and textureHandle comes from a constant buffer. + // Constant pattern: + // - combinedHandle = samplerHandleConstant | textureHandle; + // Where samplerHandleConstant is a constant value, and textureHandle comes from a constant buffer. + if (src0.AsgOp is Operation src0AsgOp) + { + if (src1.AsgOp is Operation src1AsgOp && + src0AsgOp.Inst == Instruction.BitwiseAnd && + src1AsgOp.Inst == Instruction.BitwiseAnd) { - SetHandle( - resourceManager, - gpuAccessor, - texOp, - bindlessHandle.GetCbufOffset(), - bindlessHandle.GetCbufSlot(), - rewriteSamplerType, - isImage: false); + src0 = GetSourceForMaskedHandle(src0AsgOp, 0xFFFFF); + src1 = GetSourceForMaskedHandle(src1AsgOp, 0xFFF00000); - continue; - } - - if (!TryGetOperation(bindlessHandle.AsgOp, out Operation handleCombineOp)) - { - continue; - } - - if (handleCombineOp.Inst != Instruction.BitwiseOr) - { - continue; - } - - Operand src0 = Utils.FindLastOperation(handleCombineOp.GetSource(0), block); - Operand src1 = Utils.FindLastOperation(handleCombineOp.GetSource(1), block); - - // For cases where we have a constant, ensure that the constant is always - // the second operand. - // Since this is a commutative operation, both are fine, - // and having a "canonical" representation simplifies some checks below. - if (src0.Type == OperandType.Constant && src1.Type != OperandType.Constant) - { - (src0, src1) = (src1, src0); - } - - TextureHandleType handleType = TextureHandleType.SeparateSamplerHandle; - - // Try to match the following patterns: - // Masked pattern: - // - samplerHandle = samplerHandle & 0xFFF00000; - // - textureHandle = textureHandle & 0xFFFFF; - // - combinedHandle = samplerHandle | textureHandle; - // Where samplerHandle and textureHandle comes from a constant buffer. - // Shifted pattern: - // - samplerHandle = samplerId << 20; - // - combinedHandle = samplerHandle | textureHandle; - // Where samplerId and textureHandle comes from a constant buffer. - // Constant pattern: - // - combinedHandle = samplerHandleConstant | textureHandle; - // Where samplerHandleConstant is a constant value, and textureHandle comes from a constant buffer. - if (src0.AsgOp is Operation src0AsgOp) - { - if (src1.AsgOp is Operation src1AsgOp && - src0AsgOp.Inst == Instruction.BitwiseAnd && - src1AsgOp.Inst == Instruction.BitwiseAnd) + // The OR operation is commutative, so we can also try to swap the operands to get a match. + if (src0 == null || src1 == null) { - src0 = GetSourceForMaskedHandle(src0AsgOp, 0xFFFFF); - src1 = GetSourceForMaskedHandle(src1AsgOp, 0xFFF00000); - - // The OR operation is commutative, so we can also try to swap the operands to get a match. - if (src0 == null || src1 == null) - { - src0 = GetSourceForMaskedHandle(src1AsgOp, 0xFFFFF); - src1 = GetSourceForMaskedHandle(src0AsgOp, 0xFFF00000); - } - - if (src0 == null || src1 == null) - { - continue; - } + src0 = GetSourceForMaskedHandle(src1AsgOp, 0xFFFFF); + src1 = GetSourceForMaskedHandle(src0AsgOp, 0xFFF00000); } - else if (src0AsgOp.Inst == Instruction.ShiftLeft) - { - Operand shift = src0AsgOp.GetSource(1); - if (shift.Type == OperandType.Constant && shift.Value == 20) - { - src0 = src1; - src1 = src0AsgOp.GetSource(0); - handleType = TextureHandleType.SeparateSamplerId; - } + if (src0 == null || src1 == null) + { + return false; } } - else if (src1.AsgOp is Operation src1AsgOp && src1AsgOp.Inst == Instruction.ShiftLeft) + else if (src0AsgOp.Inst == Instruction.ShiftLeft) { - Operand shift = src1AsgOp.GetSource(1); + Operand shift = src0AsgOp.GetSource(1); if (shift.Type == OperandType.Constant && shift.Value == 20) { - src1 = src1AsgOp.GetSource(0); + src0 = src1; + src1 = src0AsgOp.GetSource(0); handleType = TextureHandleType.SeparateSamplerId; } } - else if (src1.Type == OperandType.Constant && (src1.Value & 0xfffff) == 0) - { - handleType = TextureHandleType.SeparateConstantSamplerHandle; - } + } + else if (src1.AsgOp is Operation src1AsgOp && src1AsgOp.Inst == Instruction.ShiftLeft) + { + Operand shift = src1AsgOp.GetSource(1); - if (src0.Type != OperandType.ConstantBuffer) + if (shift.Type == OperandType.Constant && shift.Value == 20) { - continue; - } - - if (handleType == TextureHandleType.SeparateConstantSamplerHandle) - { - SetHandle( - resourceManager, - gpuAccessor, - texOp, - TextureHandle.PackOffsets(src0.GetCbufOffset(), ((src1.Value >> 20) & 0xfff), handleType), - TextureHandle.PackSlots(src0.GetCbufSlot(), 0), - rewriteSamplerType, - isImage: false); - } - else if (src1.Type == OperandType.ConstantBuffer) - { - SetHandle( - resourceManager, - gpuAccessor, - texOp, - TextureHandle.PackOffsets(src0.GetCbufOffset(), src1.GetCbufOffset(), handleType), - TextureHandle.PackSlots(src0.GetCbufSlot(), src1.GetCbufSlot()), - rewriteSamplerType, - isImage: false); + src1 = src1AsgOp.GetSource(0); + handleType = TextureHandleType.SeparateSamplerId; } } - else if (texOp.Inst == Instruction.ImageLoad || - texOp.Inst == Instruction.ImageStore || - texOp.Inst == Instruction.ImageAtomic) + else if (src1.Type == OperandType.Constant && (src1.Value & 0xfffff) == 0) { - Operand src0 = Utils.FindLastOperation(texOp.GetSource(0), block); + handleType = TextureHandleType.SeparateConstantSamplerHandle; + } - if (src0.Type == OperandType.ConstantBuffer) - { - int cbufOffset = src0.GetCbufOffset(); - int cbufSlot = src0.GetCbufSlot(); + if (src0.Type != OperandType.ConstantBuffer) + { + return false; + } - if (texOp.Format == TextureFormat.Unknown) - { - if (texOp.Inst == Instruction.ImageAtomic) - { - texOp.Format = ShaderProperties.GetTextureFormatAtomic(gpuAccessor, cbufOffset, cbufSlot); - } - else - { - texOp.Format = ShaderProperties.GetTextureFormat(gpuAccessor, cbufOffset, cbufSlot); - } - } + if (handleType == TextureHandleType.SeparateConstantSamplerHandle) + { + SetHandle( + resourceManager, + gpuAccessor, + texOp, + TextureHandle.PackOffsets(src0.GetCbufOffset(), ((src1.Value >> 20) & 0xfff), handleType), + TextureHandle.PackSlots(src0.GetCbufSlot(), 0), + rewriteSamplerType, + isImage: false); - bool rewriteSamplerType = texOp.Type == SamplerType.TextureBuffer; + return true; + } + else if (src1.Type == OperandType.ConstantBuffer) + { + SetHandle( + resourceManager, + gpuAccessor, + texOp, + TextureHandle.PackOffsets(src0.GetCbufOffset(), src1.GetCbufOffset(), handleType), + TextureHandle.PackSlots(src0.GetCbufSlot(), src1.GetCbufSlot()), + rewriteSamplerType, + isImage: false); - SetHandle(resourceManager, gpuAccessor, texOp, cbufOffset, cbufSlot, rewriteSamplerType, isImage: true); - } + return true; } } + else if (texOp.Inst.IsImage()) + { + Operand src0 = Utils.FindLastOperation(texOp.GetSource(0), block); + + if (src0.Type == OperandType.ConstantBuffer) + { + int cbufOffset = src0.GetCbufOffset(); + int cbufSlot = src0.GetCbufSlot(); + + if (texOp.Format == TextureFormat.Unknown) + { + if (texOp.Inst == Instruction.ImageAtomic) + { + texOp.Format = ShaderProperties.GetTextureFormatAtomic(gpuAccessor, cbufOffset, cbufSlot); + } + else + { + texOp.Format = ShaderProperties.GetTextureFormat(gpuAccessor, cbufOffset, cbufSlot); + } + } + + bool rewriteSamplerType = texOp.Type == SamplerType.TextureBuffer; + + SetHandle(resourceManager, gpuAccessor, texOp, cbufOffset, cbufSlot, rewriteSamplerType, isImage: true); + + return true; + } + } + + return false; } private static bool TryGetOperation(INode asgOp, out Operation outOperation) diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs new file mode 100644 index 0000000000..7543d1c242 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs @@ -0,0 +1,236 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; +using System.Collections.Generic; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class BindlessToArray + { + private const int NvnTextureBufferIndex = 2; + private const int HardcodedArrayLengthOgl = 4; + + // 1 and 0 elements are not considered arrays anymore. + private const int MinimumArrayLength = 2; + + public static void RunPassOgl(BasicBlock block, ResourceManager resourceManager) + { + // We can turn a bindless texture access into a indexed access, + // as long the following conditions are true: + // - The handle is loaded using a LDC instruction. + // - The handle is loaded from the constant buffer with the handles (CB2 for NVN). + // - The load has a constant offset. + // The base offset of the array of handles on the constant buffer is the constant offset. + for (LinkedListNode node = block.Operations.First; node != null; node = node.Next) + { + if (node.Value is not TextureOperation texOp) + { + continue; + } + + if ((texOp.Flags & TextureFlags.Bindless) == 0) + { + continue; + } + + if (texOp.GetSource(0).AsgOp is not Operation handleAsgOp) + { + continue; + } + + if (handleAsgOp.Inst != Instruction.Load || + handleAsgOp.StorageKind != StorageKind.ConstantBuffer || + handleAsgOp.SourcesCount != 4) + { + continue; + } + + Operand ldcSrc0 = handleAsgOp.GetSource(0); + + if (ldcSrc0.Type != OperandType.Constant || + !resourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int src0CbufSlot) || + src0CbufSlot != NvnTextureBufferIndex) + { + continue; + } + + Operand ldcSrc1 = handleAsgOp.GetSource(1); + + // We expect field index 0 to be accessed. + if (ldcSrc1.Type != OperandType.Constant || ldcSrc1.Value != 0) + { + continue; + } + + Operand ldcSrc2 = handleAsgOp.GetSource(2); + + // FIXME: This is missing some checks, for example, a check to ensure that the shift value is 2. + // Might be not worth fixing since if that doesn't kick in, the result will be no texture + // to access anyway which is also wrong. + // Plus this whole transform is fundamentally flawed as-is since we have no way to know the array size. + // Eventually, this should be entirely removed in favor of a implementation that supports true bindless + // texture access. + if (ldcSrc2.AsgOp is not Operation shrOp || shrOp.Inst != Instruction.ShiftRightU32) + { + continue; + } + + if (shrOp.GetSource(0).AsgOp is not Operation shrOp2 || shrOp2.Inst != Instruction.ShiftRightU32) + { + continue; + } + + if (shrOp2.GetSource(0).AsgOp is not Operation addOp || addOp.Inst != Instruction.Add) + { + continue; + } + + Operand addSrc1 = addOp.GetSource(1); + + if (addSrc1.Type != OperandType.Constant) + { + continue; + } + + TurnIntoArray(resourceManager, texOp, NvnTextureBufferIndex, addSrc1.Value / 4, HardcodedArrayLengthOgl); + + Operand index = Local(); + + Operand source = addOp.GetSource(0); + + Operation shrBy3 = new(Instruction.ShiftRightU32, index, source, Const(3)); + + block.Operations.AddBefore(node, shrBy3); + + texOp.SetSource(0, index); + } + } + + public static void RunPass(BasicBlock block, ResourceManager resourceManager, IGpuAccessor gpuAccessor) + { + // We can turn a bindless texture access into a indexed access, + // as long the following conditions are true: + // - The handle is loaded using a LDC instruction. + // - The handle is loaded from the constant buffer with the handles (CB2 for NVN). + // - The load has a constant offset. + // The base offset of the array of handles on the constant buffer is the constant offset. + for (LinkedListNode node = block.Operations.First; node != null; node = node.Next) + { + if (node.Value is not TextureOperation texOp) + { + continue; + } + + if ((texOp.Flags & TextureFlags.Bindless) == 0) + { + continue; + } + + if (texOp.GetSource(0).AsgOp is not Operation handleAsgOp) + { + continue; + } + + int secondaryCbufSlot = 0; + int secondaryCbufOffset = 0; + bool hasSecondaryHandle = false; + + if (handleAsgOp.Inst == Instruction.BitwiseOr) + { + Operand src0 = handleAsgOp.GetSource(0); + Operand src1 = handleAsgOp.GetSource(1); + + if (src0.Type == OperandType.ConstantBuffer && src1.AsgOp is Operation) + { + handleAsgOp = src1.AsgOp as Operation; + secondaryCbufSlot = src0.GetCbufSlot(); + secondaryCbufOffset = src0.GetCbufOffset(); + hasSecondaryHandle = true; + } + else if (src0.AsgOp is Operation && src1.Type == OperandType.ConstantBuffer) + { + handleAsgOp = src0.AsgOp as Operation; + secondaryCbufSlot = src1.GetCbufSlot(); + secondaryCbufOffset = src1.GetCbufOffset(); + hasSecondaryHandle = true; + } + } + + if (handleAsgOp.Inst != Instruction.Load || + handleAsgOp.StorageKind != StorageKind.ConstantBuffer || + handleAsgOp.SourcesCount != 4) + { + continue; + } + + Operand ldcSrc0 = handleAsgOp.GetSource(0); + + if (ldcSrc0.Type != OperandType.Constant || + !resourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int src0CbufSlot)) + { + continue; + } + + Operand ldcSrc1 = handleAsgOp.GetSource(1); + + // We expect field index 0 to be accessed. + if (ldcSrc1.Type != OperandType.Constant || ldcSrc1.Value != 0) + { + continue; + } + + Operand ldcVecIndex = handleAsgOp.GetSource(2); + Operand ldcElemIndex = handleAsgOp.GetSource(3); + + if (ldcVecIndex.Type != OperandType.LocalVariable || ldcElemIndex.Type != OperandType.LocalVariable) + { + continue; + } + + int cbufSlot; + int handleIndex; + + if (hasSecondaryHandle) + { + cbufSlot = TextureHandle.PackSlots(src0CbufSlot, secondaryCbufSlot); + handleIndex = TextureHandle.PackOffsets(0, secondaryCbufOffset, TextureHandleType.SeparateSamplerHandle); + } + else + { + cbufSlot = src0CbufSlot; + handleIndex = 0; + } + + int length = Math.Max(MinimumArrayLength, gpuAccessor.QueryTextureArrayLengthFromBuffer(src0CbufSlot)); + + TurnIntoArray(resourceManager, texOp, cbufSlot, handleIndex, length); + + Operand vecIndex = Local(); + Operand elemIndex = Local(); + Operand index = Local(); + Operand indexMin = Local(); + + block.Operations.AddBefore(node, new Operation(Instruction.ShiftLeft, vecIndex, ldcVecIndex, Const(1))); + block.Operations.AddBefore(node, new Operation(Instruction.ShiftRightU32, elemIndex, ldcElemIndex, Const(1))); + block.Operations.AddBefore(node, new Operation(Instruction.Add, index, vecIndex, elemIndex)); + block.Operations.AddBefore(node, new Operation(Instruction.MinimumU32, indexMin, index, Const(length - 1))); + + texOp.SetSource(0, indexMin); + } + } + + private static void TurnIntoArray(ResourceManager resourceManager, TextureOperation texOp, int cbufSlot, int handleIndex, int length) + { + int binding = resourceManager.GetTextureOrImageBinding( + texOp.Inst, + texOp.Type, + texOp.Format, + texOp.Flags & ~TextureFlags.Bindless, + cbufSlot, + handleIndex, + length); + + texOp.TurnIntoArray(binding); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToIndexed.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToIndexed.cs deleted file mode 100644 index 2bd31fe1b8..0000000000 --- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToIndexed.cs +++ /dev/null @@ -1,118 +0,0 @@ -using Ryujinx.Graphics.Shader.IntermediateRepresentation; -using System.Collections.Generic; - -using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; - -namespace Ryujinx.Graphics.Shader.Translation.Optimizations -{ - static class BindlessToIndexed - { - private const int NvnTextureBufferIndex = 2; - - public static void RunPass(BasicBlock block, ResourceManager resourceManager) - { - // We can turn a bindless texture access into a indexed access, - // as long the following conditions are true: - // - The handle is loaded using a LDC instruction. - // - The handle is loaded from the constant buffer with the handles (CB2 for NVN). - // - The load has a constant offset. - // The base offset of the array of handles on the constant buffer is the constant offset. - for (LinkedListNode node = block.Operations.First; node != null; node = node.Next) - { - if (node.Value is not TextureOperation texOp) - { - continue; - } - - if ((texOp.Flags & TextureFlags.Bindless) == 0) - { - continue; - } - - if (texOp.GetSource(0).AsgOp is not Operation handleAsgOp) - { - continue; - } - - if (handleAsgOp.Inst != Instruction.Load || - handleAsgOp.StorageKind != StorageKind.ConstantBuffer || - handleAsgOp.SourcesCount != 4) - { - continue; - } - - Operand ldcSrc0 = handleAsgOp.GetSource(0); - - if (ldcSrc0.Type != OperandType.Constant || - !resourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int src0CbufSlot) || - src0CbufSlot != NvnTextureBufferIndex) - { - continue; - } - - Operand ldcSrc1 = handleAsgOp.GetSource(1); - - // We expect field index 0 to be accessed. - if (ldcSrc1.Type != OperandType.Constant || ldcSrc1.Value != 0) - { - continue; - } - - Operand ldcSrc2 = handleAsgOp.GetSource(2); - - // FIXME: This is missing some checks, for example, a check to ensure that the shift value is 2. - // Might be not worth fixing since if that doesn't kick in, the result will be no texture - // to access anyway which is also wrong. - // Plus this whole transform is fundamentally flawed as-is since we have no way to know the array size. - // Eventually, this should be entirely removed in favor of a implementation that supports true bindless - // texture access. - if (ldcSrc2.AsgOp is not Operation shrOp || shrOp.Inst != Instruction.ShiftRightU32) - { - continue; - } - - if (shrOp.GetSource(0).AsgOp is not Operation shrOp2 || shrOp2.Inst != Instruction.ShiftRightU32) - { - continue; - } - - if (shrOp2.GetSource(0).AsgOp is not Operation addOp || addOp.Inst != Instruction.Add) - { - continue; - } - - Operand addSrc1 = addOp.GetSource(1); - - if (addSrc1.Type != OperandType.Constant) - { - continue; - } - - TurnIntoIndexed(resourceManager, texOp, addSrc1.Value / 4); - - Operand index = Local(); - - Operand source = addOp.GetSource(0); - - Operation shrBy3 = new(Instruction.ShiftRightU32, index, source, Const(3)); - - block.Operations.AddBefore(node, shrBy3); - - texOp.SetSource(0, index); - } - } - - private static void TurnIntoIndexed(ResourceManager resourceManager, TextureOperation texOp, int handle) - { - int binding = resourceManager.GetTextureOrImageBinding( - texOp.Inst, - texOp.Type | SamplerType.Indexed, - texOp.Format, - texOp.Flags & ~TextureFlags.Bindless, - NvnTextureBufferIndex, - handle); - - texOp.TurnIntoIndexed(binding); - } - } -} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs index ea06691ba9..49eb3a89b3 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs @@ -20,7 +20,15 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations // Those passes are looking for specific patterns and only needs to run once. for (int blkIndex = 0; blkIndex < context.Blocks.Length; blkIndex++) { - BindlessToIndexed.RunPass(context.Blocks[blkIndex], context.ResourceManager); + if (context.TargetApi == TargetApi.OpenGL) + { + BindlessToArray.RunPassOgl(context.Blocks[blkIndex], context.ResourceManager); + } + else + { + BindlessToArray.RunPass(context.Blocks[blkIndex], context.ResourceManager, context.GpuAccessor); + } + BindlessElimination.RunPass(context.Blocks[blkIndex], context.ResourceManager, context.GpuAccessor); // FragmentCoord only exists on fragment shaders, so we don't need to check other stages. diff --git a/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs b/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs index 83332711fc..e9fe0b1ee5 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs @@ -14,9 +14,6 @@ namespace Ryujinx.Graphics.Shader.Translation private const int DefaultLocalMemorySize = 128; private const int DefaultSharedMemorySize = 4096; - // TODO: Non-hardcoded array size. - public const int SamplerArraySize = 4; - private static readonly string[] _stagePrefixes = new string[] { "cp", "vp", "tcp", "tep", "gp", "fp" }; private readonly IGpuAccessor _gpuAccessor; @@ -32,7 +29,7 @@ namespace Ryujinx.Graphics.Shader.Translation private readonly HashSet _usedConstantBufferBindings; - private readonly record struct TextureInfo(int CbufSlot, int Handle, bool Indexed, TextureFormat Format); + private readonly record struct TextureInfo(int CbufSlot, int Handle, int ArrayLength, SamplerType Type, TextureFormat Format); private struct TextureMeta { @@ -152,7 +149,7 @@ namespace Ryujinx.Graphics.Shader.Translation int binding = _cbSlotToBindingMap[slot]; if (binding < 0) { - binding = _gpuAccessor.QueryBindingConstantBuffer(slot); + binding = _gpuAccessor.CreateConstantBufferBinding(slot); _cbSlotToBindingMap[slot] = binding; string slotNumber = slot.ToString(CultureInfo.InvariantCulture); AddNewConstantBuffer(binding, $"{_stagePrefix}_c{slotNumber}"); @@ -173,7 +170,7 @@ namespace Ryujinx.Graphics.Shader.Translation if (binding < 0) { - binding = _gpuAccessor.QueryBindingStorageBuffer(slot); + binding = _gpuAccessor.CreateStorageBufferBinding(slot); _sbSlotToBindingMap[slot] = binding; string slotNumber = slot.ToString(CultureInfo.InvariantCulture); AddNewStorageBuffer(binding, $"{_stagePrefix}_s{slotNumber}"); @@ -227,11 +224,12 @@ namespace Ryujinx.Graphics.Shader.Translation TextureFormat format, TextureFlags flags, int cbufSlot, - int handle) + int handle, + int arrayLength = 1) { inst &= Instruction.Mask; - bool isImage = inst == Instruction.ImageLoad || inst == Instruction.ImageStore || inst == Instruction.ImageAtomic; - bool isWrite = inst == Instruction.ImageStore || inst == Instruction.ImageAtomic; + bool isImage = inst.IsImage(); + bool isWrite = inst.IsImageStore(); bool accurateType = !inst.IsTextureQuery(); bool intCoords = isImage || flags.HasFlag(TextureFlags.IntCoords) || inst == Instruction.TextureQuerySize; bool coherent = flags.HasFlag(TextureFlags.Coherent); @@ -241,7 +239,7 @@ namespace Ryujinx.Graphics.Shader.Translation format = TextureFormat.Unknown; } - int binding = GetTextureOrImageBinding(cbufSlot, handle, type, format, isImage, intCoords, isWrite, accurateType, coherent); + int binding = GetTextureOrImageBinding(cbufSlot, handle, arrayLength, type, format, isImage, intCoords, isWrite, accurateType, coherent); _gpuAccessor.RegisterTexture(handle, cbufSlot); @@ -251,6 +249,7 @@ namespace Ryujinx.Graphics.Shader.Translation private int GetTextureOrImageBinding( int cbufSlot, int handle, + int arrayLength, SamplerType type, TextureFormat format, bool isImage, @@ -260,7 +259,6 @@ namespace Ryujinx.Graphics.Shader.Translation bool coherent) { var dimensions = type.GetDimensions(); - var isIndexed = type.HasFlag(SamplerType.Indexed); var dict = isImage ? _usedImages : _usedTextures; var usageFlags = TextureUsageFlags.None; @@ -269,7 +267,7 @@ namespace Ryujinx.Graphics.Shader.Translation { usageFlags |= TextureUsageFlags.NeedsScaleValue; - var canScale = _stage.SupportsRenderScale() && !isIndexed && !write && dimensions == 2; + var canScale = _stage.SupportsRenderScale() && arrayLength == 1 && !write && dimensions == 2; if (!canScale) { @@ -289,76 +287,75 @@ namespace Ryujinx.Graphics.Shader.Translation usageFlags |= TextureUsageFlags.ImageCoherent; } - int arraySize = isIndexed ? SamplerArraySize : 1; - int firstBinding = -1; - - for (int layer = 0; layer < arraySize; layer++) + // For array textures, we also want to use type as key, + // since we may have texture handles stores in the same buffer, but for textures with different types. + var keyType = arrayLength > 1 ? type : SamplerType.None; + var info = new TextureInfo(cbufSlot, handle, arrayLength, keyType, format); + var meta = new TextureMeta() { - var info = new TextureInfo(cbufSlot, handle + layer * 2, isIndexed, format); - var meta = new TextureMeta() - { - AccurateType = accurateType, - Type = type, - UsageFlags = usageFlags, - }; + AccurateType = accurateType, + Type = type, + UsageFlags = usageFlags, + }; - int binding; + int binding; - if (dict.TryGetValue(info, out var existingMeta)) - { - dict[info] = MergeTextureMeta(meta, existingMeta); - binding = existingMeta.Binding; - } - else - { - bool isBuffer = (type & SamplerType.Mask) == SamplerType.TextureBuffer; + if (dict.TryGetValue(info, out var existingMeta)) + { + dict[info] = MergeTextureMeta(meta, existingMeta); + binding = existingMeta.Binding; + } + else + { + bool isBuffer = (type & SamplerType.Mask) == SamplerType.TextureBuffer; - binding = isImage - ? _gpuAccessor.QueryBindingImage(dict.Count, isBuffer) - : _gpuAccessor.QueryBindingTexture(dict.Count, isBuffer); + binding = isImage + ? _gpuAccessor.CreateImageBinding(arrayLength, isBuffer) + : _gpuAccessor.CreateTextureBinding(arrayLength, isBuffer); - meta.Binding = binding; + meta.Binding = binding; - dict.Add(info, meta); - } - - string nameSuffix; - - if (isImage) - { - nameSuffix = cbufSlot < 0 - ? $"i_tcb_{handle:X}_{format.ToGlslFormat()}" - : $"i_cb{cbufSlot}_{handle:X}_{format.ToGlslFormat()}"; - } - else - { - nameSuffix = cbufSlot < 0 ? $"t_tcb_{handle:X}" : $"t_cb{cbufSlot}_{handle:X}"; - } - - var definition = new TextureDefinition( - isImage ? 3 : 2, - binding, - $"{_stagePrefix}_{nameSuffix}", - meta.Type, - info.Format, - meta.UsageFlags); - - if (isImage) - { - Properties.AddOrUpdateImage(definition); - } - else - { - Properties.AddOrUpdateTexture(definition); - } - - if (layer == 0) - { - firstBinding = binding; - } + dict.Add(info, meta); } - return firstBinding; + string nameSuffix; + string prefix = isImage ? "i" : "t"; + + if (arrayLength != 1 && type != SamplerType.None) + { + prefix += type.ToShortSamplerType(); + } + + if (isImage) + { + nameSuffix = cbufSlot < 0 + ? $"{prefix}_tcb_{handle:X}_{format.ToGlslFormat()}" + : $"{prefix}_cb{cbufSlot}_{handle:X}_{format.ToGlslFormat()}"; + } + else + { + nameSuffix = cbufSlot < 0 ? $"{prefix}_tcb_{handle:X}" : $"{prefix}_cb{cbufSlot}_{handle:X}"; + } + + var definition = new TextureDefinition( + isImage ? 3 : 2, + binding, + arrayLength, + $"{_stagePrefix}_{nameSuffix}", + meta.Type, + info.Format, + meta.UsageFlags); + + if (isImage) + { + Properties.AddOrUpdateImage(definition); + } + else + { + Properties.AddOrUpdateTexture(definition); + } + + return binding; } private static TextureMeta MergeTextureMeta(TextureMeta meta, TextureMeta existingMeta) @@ -399,8 +396,7 @@ namespace Ryujinx.Graphics.Shader.Translation selectedMeta.UsageFlags |= TextureUsageFlags.NeedsScaleValue; var dimensions = type.GetDimensions(); - var isIndexed = type.HasFlag(SamplerType.Indexed); - var canScale = _stage.SupportsRenderScale() && !isIndexed && dimensions == 2; + var canScale = _stage.SupportsRenderScale() && selectedInfo.ArrayLength == 1 && dimensions == 2; if (!canScale) { @@ -468,34 +464,61 @@ namespace Ryujinx.Graphics.Shader.Translation return descriptors; } - public TextureDescriptor[] GetTextureDescriptors() + public TextureDescriptor[] GetTextureDescriptors(bool includeArrays = true) { - return GetDescriptors(_usedTextures, _usedTextures.Count); + return GetDescriptors(_usedTextures, includeArrays); } - public TextureDescriptor[] GetImageDescriptors() + public TextureDescriptor[] GetImageDescriptors(bool includeArrays = true) { - return GetDescriptors(_usedImages, _usedImages.Count); + return GetDescriptors(_usedImages, includeArrays); } - private static TextureDescriptor[] GetDescriptors(IReadOnlyDictionary usedResources, int count) + private static TextureDescriptor[] GetDescriptors(IReadOnlyDictionary usedResources, bool includeArrays) { - TextureDescriptor[] descriptors = new TextureDescriptor[count]; + List descriptors = new(); - int descriptorIndex = 0; + bool hasAnyArray = false; foreach ((TextureInfo info, TextureMeta meta) in usedResources) { - descriptors[descriptorIndex++] = new TextureDescriptor( + if (info.ArrayLength > 1) + { + hasAnyArray = true; + continue; + } + + descriptors.Add(new TextureDescriptor( meta.Binding, meta.Type, info.Format, info.CbufSlot, info.Handle, - meta.UsageFlags); + info.ArrayLength, + meta.UsageFlags)); } - return descriptors; + if (hasAnyArray && includeArrays) + { + foreach ((TextureInfo info, TextureMeta meta) in usedResources) + { + if (info.ArrayLength <= 1) + { + continue; + } + + descriptors.Add(new TextureDescriptor( + meta.Binding, + meta.Type, + info.Format, + info.CbufSlot, + info.Handle, + info.ArrayLength, + meta.UsageFlags)); + } + } + + return descriptors.ToArray(); } public bool TryGetCbufSlotAndHandleForTexture(int binding, out int cbufSlot, out int handle) @@ -531,6 +554,19 @@ namespace Ryujinx.Graphics.Shader.Translation return FindDescriptorIndex(GetImageDescriptors(), binding); } + public bool IsArrayOfTexturesOrImages(int binding, bool isImage) + { + foreach ((TextureInfo info, TextureMeta meta) in isImage ? _usedImages : _usedTextures) + { + if (meta.Binding == binding) + { + return info.ArrayLength != 1; + } + } + + return false; + } + private void AddNewConstantBuffer(int binding, string name) { StructureType type = new(new[] diff --git a/src/Ryujinx.Graphics.Shader/Translation/TransformContext.cs b/src/Ryujinx.Graphics.Shader/Translation/TransformContext.cs index 87ebb8e7cc..1e87585f18 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/TransformContext.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/TransformContext.cs @@ -9,6 +9,7 @@ namespace Ryujinx.Graphics.Shader.Translation public readonly ShaderDefinitions Definitions; public readonly ResourceManager ResourceManager; public readonly IGpuAccessor GpuAccessor; + public readonly TargetApi TargetApi; public readonly TargetLanguage TargetLanguage; public readonly ShaderStage Stage; public readonly ref FeatureFlags UsedFeatures; @@ -19,6 +20,7 @@ namespace Ryujinx.Graphics.Shader.Translation ShaderDefinitions definitions, ResourceManager resourceManager, IGpuAccessor gpuAccessor, + TargetApi targetApi, TargetLanguage targetLanguage, ShaderStage stage, ref FeatureFlags usedFeatures) @@ -28,6 +30,7 @@ namespace Ryujinx.Graphics.Shader.Translation Definitions = definitions; ResourceManager = resourceManager; GpuAccessor = gpuAccessor; + TargetApi = targetApi; TargetLanguage = targetLanguage; Stage = stage; UsedFeatures = ref usedFeatures; diff --git a/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs b/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs index 495ea8a94c..072b456955 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs @@ -23,7 +23,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms { node = InsertCoordNormalization(context.Hfm, node, context.ResourceManager, context.GpuAccessor, context.Stage); node = InsertCoordGatherBias(node, context.ResourceManager, context.GpuAccessor); - node = InsertConstOffsets(node, context.GpuAccessor, context.Stage); + node = InsertConstOffsets(node, context.ResourceManager, context.GpuAccessor, context.Stage); if (texOp.Type == SamplerType.TextureBuffer && !context.GpuAccessor.QueryHostSupportsSnormBufferTextureFormat()) { @@ -45,13 +45,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - - int coordsCount = texOp.Type.GetDimensions(); - - int coordsIndex = isBindless || isIndexed ? 1 : 0; bool isImage = IsImageInstructionWithScale(texOp.Inst); + bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage); if ((texOp.Inst == Instruction.TextureSample || isImage) && (intCoords || isImage) && @@ -62,9 +58,12 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms { int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TexelFetchScale); int samplerIndex = isImage - ? resourceManager.GetTextureDescriptors().Length + resourceManager.FindImageDescriptorIndex(texOp.Binding) + ? resourceManager.GetTextureDescriptors(includeArrays: false).Length + resourceManager.FindImageDescriptorIndex(texOp.Binding) : resourceManager.FindTextureDescriptorIndex(texOp.Binding); + int coordsCount = texOp.Type.GetDimensions(); + int coordsIndex = isBindless ? 1 : 0; + for (int index = 0; index < coordsCount; index++) { Operand scaledCoord = Local(); @@ -97,7 +96,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms TextureOperation texOp = (TextureOperation)node.Value; bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; + bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage: false); if (texOp.Inst == Instruction.TextureQuerySize && texOp.Index < 2 && @@ -152,8 +151,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms TextureOperation texOp = (TextureOperation)node.Value; bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage: false); - if (isBindless || !resourceManager.TryGetCbufSlotAndHandleForTexture(texOp.Binding, out int cbufSlot, out int handle)) + if (isBindless || isIndexed || !resourceManager.TryGetCbufSlotAndHandleForTexture(texOp.Binding, out int cbufSlot, out int handle)) { return node; } @@ -167,10 +167,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms return node; } - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - int coordsCount = texOp.Type.GetDimensions(); - int coordsIndex = isBindless || isIndexed ? 1 : 0; int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount; @@ -178,16 +175,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms { Operand coordSize = Local(); - Operand[] texSizeSources; - - if (isBindless || isIndexed) - { - texSizeSources = new Operand[] { texOp.GetSource(0), Const(0) }; - } - else - { - texSizeSources = new Operand[] { Const(0) }; - } + Operand[] texSizeSources = new Operand[] { Const(0) }; LinkedListNode textureSizeNode = node.List.AddBefore(node, new TextureOperation( Instruction.TextureQuerySize, @@ -201,13 +189,13 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms resourceManager.SetUsageFlagsForTextureQuery(texOp.Binding, texOp.Type); - Operand source = texOp.GetSource(coordsIndex + index); + Operand source = texOp.GetSource(index); Operand coordNormalized = Local(); node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Divide, coordNormalized, source, GenerateI2f(node, coordSize))); - texOp.SetSource(coordsIndex + index, coordNormalized); + texOp.SetSource(index, coordNormalized); InsertTextureSizeUnscale(hfm, textureSizeNode, resourceManager, stage); } @@ -234,7 +222,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms return node; } - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; + bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage: false); int coordsCount = texOp.Type.GetDimensions(); int coordsIndex = isBindless || isIndexed ? 1 : 0; @@ -287,7 +275,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms return node; } - private static LinkedListNode InsertConstOffsets(LinkedListNode node, IGpuAccessor gpuAccessor, ShaderStage stage) + private static LinkedListNode InsertConstOffsets(LinkedListNode node, ResourceManager resourceManager, IGpuAccessor gpuAccessor, ShaderStage stage) { // Non-constant texture offsets are not allowed (according to the spec), // however some GPUs does support that. @@ -321,7 +309,6 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms bool hasLodLevel = (texOp.Flags & TextureFlags.LodLevel) != 0; bool isArray = (texOp.Type & SamplerType.Array) != 0; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0; bool isShadow = (texOp.Type & SamplerType.Shadow) != 0; @@ -342,6 +329,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms offsetsCount = 0; } + bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage: false); + Operand[] offsets = new Operand[offsetsCount]; Operand[] sources = new Operand[texOp.SourcesCount - offsetsCount]; diff --git a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs index a193ab3c4d..581f4372c4 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs @@ -294,6 +294,7 @@ namespace Ryujinx.Graphics.Shader.Translation Definitions, resourceManager, GpuAccessor, + Options.TargetApi, Options.TargetLanguage, Definitions.Stage, ref usedFeatures); @@ -412,7 +413,7 @@ namespace Ryujinx.Graphics.Shader.Translation if (Stage == ShaderStage.Vertex) { int ibBinding = resourceManager.Reservations.IndexBufferTextureBinding; - TextureDefinition indexBuffer = new(2, ibBinding, "ib_data", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None); + TextureDefinition indexBuffer = new(2, ibBinding, 1, "ib_data", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None); resourceManager.Properties.AddOrUpdateTexture(indexBuffer); int inputMap = _program.AttributeUsage.UsedInputAttributes; @@ -421,7 +422,7 @@ namespace Ryujinx.Graphics.Shader.Translation { int location = BitOperations.TrailingZeroCount(inputMap); int binding = resourceManager.Reservations.GetVertexBufferTextureBinding(location); - TextureDefinition vaBuffer = new(2, binding, $"vb_data{location}", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None); + TextureDefinition vaBuffer = new(2, binding, 1, $"vb_data{location}", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None); resourceManager.Properties.AddOrUpdateTexture(vaBuffer); inputMap &= ~(1 << location); @@ -430,7 +431,7 @@ namespace Ryujinx.Graphics.Shader.Translation else if (Stage == ShaderStage.Geometry) { int trbBinding = resourceManager.Reservations.TopologyRemapBufferTextureBinding; - TextureDefinition remapBuffer = new(2, trbBinding, "trb_data", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None); + TextureDefinition remapBuffer = new(2, trbBinding, 1, "trb_data", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None); resourceManager.Properties.AddOrUpdateTexture(remapBuffer); int geometryVbOutputSbBinding = resourceManager.Reservations.GeometryVertexOutputStorageBufferBinding; diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs index 7594384d6a..707ae12922 100644 --- a/src/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs +++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs @@ -6,7 +6,7 @@ namespace Ryujinx.Graphics.Vulkan { class DescriptorSetManager : IDisposable { - public const uint MaxSets = 16; + public const uint MaxSets = 8; public class DescriptorPoolHolder : IDisposable { diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs index 765686025a..a0299a3720 100644 --- a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs +++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs @@ -14,6 +14,9 @@ namespace Ryujinx.Graphics.Vulkan class DescriptorSetUpdater { private const ulong StorageBufferMaxMirrorable = 0x2000; + + private const int ArrayGrowthSize = 16; + private record struct BufferRef { public Auto Buffer; @@ -65,6 +68,18 @@ namespace Ryujinx.Graphics.Vulkan } } + private record struct ArrayRef + { + public ShaderStage Stage; + public T Array; + + public ArrayRef(ShaderStage stage, T array) + { + Stage = stage; + Array = array; + } + } + private readonly VulkanRenderer _gd; private readonly Device _device; private readonly PipelineBase _pipeline; @@ -78,6 +93,9 @@ namespace Ryujinx.Graphics.Vulkan private readonly TextureBuffer[] _bufferImageRefs; private readonly Format[] _bufferImageFormats; + private ArrayRef[] _textureArrayRefs; + private ArrayRef[] _imageArrayRefs; + private readonly DescriptorBufferInfo[] _uniformBuffers; private readonly DescriptorBufferInfo[] _storageBuffers; private readonly DescriptorImageInfo[] _textures; @@ -130,6 +148,9 @@ namespace Ryujinx.Graphics.Vulkan _bufferImageRefs = new TextureBuffer[Constants.MaxImageBindings * 2]; _bufferImageFormats = new Format[Constants.MaxImageBindings * 2]; + _textureArrayRefs = Array.Empty>(); + _imageArrayRefs = Array.Empty>(); + _uniformBuffers = new DescriptorBufferInfo[Constants.MaxUniformBufferBindings]; _storageBuffers = new DescriptorBufferInfo[Constants.MaxStorageBufferBindings]; _textures = new DescriptorImageInfo[Constants.MaxTexturesPerStage]; @@ -263,10 +284,18 @@ namespace Ryujinx.Graphics.Vulkan { if (segment.Type == ResourceType.TextureAndSampler) { - for (int i = 0; i < segment.Count; i++) + if (!segment.IsArray) { - ref var texture = ref _textureRefs[segment.Binding + i]; - texture.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, texture.Stage.ConvertToPipelineStageFlags()); + for (int i = 0; i < segment.Count; i++) + { + ref var texture = ref _textureRefs[segment.Binding + i]; + texture.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, texture.Stage.ConvertToPipelineStageFlags()); + } + } + else + { + PipelineStageFlags stageFlags = _textureArrayRefs[segment.Binding].Stage.ConvertToPipelineStageFlags(); + _textureArrayRefs[segment.Binding].Array?.QueueWriteToReadBarriers(cbs, stageFlags); } } } @@ -275,10 +304,18 @@ namespace Ryujinx.Graphics.Vulkan { if (segment.Type == ResourceType.Image) { - for (int i = 0; i < segment.Count; i++) + if (!segment.IsArray) { - ref var image = ref _imageRefs[segment.Binding + i]; - image.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, image.Stage.ConvertToPipelineStageFlags()); + for (int i = 0; i < segment.Count; i++) + { + ref var image = ref _imageRefs[segment.Binding + i]; + image.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, image.Stage.ConvertToPipelineStageFlags()); + } + } + else + { + PipelineStageFlags stageFlags = _imageArrayRefs[segment.Binding].Stage.ConvertToPipelineStageFlags(); + _imageArrayRefs[segment.Binding].Array?.QueueWriteToReadBarriers(cbs, stageFlags); } } } @@ -455,6 +492,58 @@ namespace Ryujinx.Graphics.Vulkan } } + public void SetTextureArray(CommandBufferScoped cbs, ShaderStage stage, int binding, ITextureArray array) + { + if (_textureArrayRefs.Length <= binding) + { + Array.Resize(ref _textureArrayRefs, binding + ArrayGrowthSize); + } + + if (_textureArrayRefs[binding].Stage != stage || _textureArrayRefs[binding].Array != array) + { + if (_textureArrayRefs[binding].Array != null) + { + _textureArrayRefs[binding].Array.Bound = false; + } + + if (array is TextureArray textureArray) + { + textureArray.Bound = true; + textureArray.QueueWriteToReadBarriers(cbs, stage.ConvertToPipelineStageFlags()); + } + + _textureArrayRefs[binding] = new ArrayRef(stage, array as TextureArray); + + SignalDirty(DirtyFlags.Texture); + } + } + + public void SetImageArray(CommandBufferScoped cbs, ShaderStage stage, int binding, IImageArray array) + { + if (_imageArrayRefs.Length <= binding) + { + Array.Resize(ref _imageArrayRefs, binding + ArrayGrowthSize); + } + + if (_imageArrayRefs[binding].Stage != stage || _imageArrayRefs[binding].Array != array) + { + if (_imageArrayRefs[binding].Array != null) + { + _imageArrayRefs[binding].Array.Bound = false; + } + + if (array is ImageArray imageArray) + { + imageArray.Bound = true; + imageArray.QueueWriteToReadBarriers(cbs, stage.ConvertToPipelineStageFlags()); + } + + _imageArrayRefs[binding] = new ArrayRef(stage, array as ImageArray); + + SignalDirty(DirtyFlags.Image); + } + } + public void SetUniformBuffers(CommandBuffer commandBuffer, ReadOnlySpan buffers) { for (int i = 0; i < buffers.Length; i++) @@ -653,66 +742,94 @@ namespace Ryujinx.Graphics.Vulkan } else if (setIndex == PipelineBase.TextureSetIndex) { - if (segment.Type != ResourceType.BufferTexture) + if (!segment.IsArray) { - Span textures = _textures; - - for (int i = 0; i < count; i++) + if (segment.Type != ResourceType.BufferTexture) { - ref var texture = ref textures[i]; - ref var refs = ref _textureRefs[binding + i]; + Span textures = _textures; - texture.ImageView = refs.View?.Get(cbs).Value ?? default; - texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default; - - if (texture.ImageView.Handle == 0) + for (int i = 0; i < count; i++) { - texture.ImageView = _dummyTexture.GetImageView().Get(cbs).Value; + ref var texture = ref textures[i]; + ref var refs = ref _textureRefs[binding + i]; + + texture.ImageView = refs.View?.Get(cbs).Value ?? default; + texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default; + + if (texture.ImageView.Handle == 0) + { + texture.ImageView = _dummyTexture.GetImageView().Get(cbs).Value; + } + + if (texture.Sampler.Handle == 0) + { + texture.Sampler = _dummySampler.GetSampler().Get(cbs).Value; + } } - if (texture.Sampler.Handle == 0) - { - texture.Sampler = _dummySampler.GetSampler().Get(cbs).Value; - } + tu.Push(textures[..count]); } + else + { + Span bufferTextures = _bufferTextures; - tu.Push(textures[..count]); + for (int i = 0; i < count; i++) + { + bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs, false) ?? default; + } + + tu.Push(bufferTextures[..count]); + } } else { - Span bufferTextures = _bufferTextures; - - for (int i = 0; i < count; i++) + if (segment.Type != ResourceType.BufferTexture) { - bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs, false) ?? default; + tu.Push(_textureArrayRefs[binding].Array.GetImageInfos(_gd, cbs, _dummyTexture, _dummySampler)); + } + else + { + tu.Push(_textureArrayRefs[binding].Array.GetBufferViews(cbs)); } - - tu.Push(bufferTextures[..count]); } } else if (setIndex == PipelineBase.ImageSetIndex) { - if (segment.Type != ResourceType.BufferImage) + if (!segment.IsArray) { - Span images = _images; - - for (int i = 0; i < count; i++) + if (segment.Type != ResourceType.BufferImage) { - images[i].ImageView = _imageRefs[binding + i].View?.Get(cbs).Value ?? default; - } + Span images = _images; - tu.Push(images[..count]); + for (int i = 0; i < count; i++) + { + images[i].ImageView = _imageRefs[binding + i].View?.Get(cbs).Value ?? default; + } + + tu.Push(images[..count]); + } + else + { + Span bufferImages = _bufferImages; + + for (int i = 0; i < count; i++) + { + bufferImages[i] = _bufferImageRefs[binding + i]?.GetBufferView(cbs, _bufferImageFormats[binding + i], true) ?? default; + } + + tu.Push(bufferImages[..count]); + } } else { - Span bufferImages = _bufferImages; - - for (int i = 0; i < count; i++) + if (segment.Type != ResourceType.BufferTexture) { - bufferImages[i] = _bufferImageRefs[binding + i]?.GetBufferView(cbs, _bufferImageFormats[binding + i], true) ?? default; + tu.Push(_imageArrayRefs[binding].Array.GetImageInfos(_gd, cbs, _dummyTexture)); + } + else + { + tu.Push(_imageArrayRefs[binding].Array.GetBufferViews(cbs)); } - - tu.Push(bufferImages[..count]); } } } @@ -825,6 +942,16 @@ namespace Ryujinx.Graphics.Vulkan AdvancePdSequence(); } + public void ForceTextureDirty() + { + SignalDirty(DirtyFlags.Texture); + } + + public void ForceImageDirty() + { + SignalDirty(DirtyFlags.Image); + } + private static void SwapBuffer(BufferRef[] list, Auto from, Auto to) { for (int i = 0; i < list.Length; i++) diff --git a/src/Ryujinx.Graphics.Vulkan/ImageArray.cs b/src/Ryujinx.Graphics.Vulkan/ImageArray.cs new file mode 100644 index 0000000000..38a5b6b481 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/ImageArray.cs @@ -0,0 +1,179 @@ +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Vulkan +{ + class ImageArray : IImageArray + { + private readonly VulkanRenderer _gd; + + private record struct TextureRef + { + public TextureStorage Storage; + public TextureView View; + public GAL.Format ImageFormat; + } + + private readonly TextureRef[] _textureRefs; + private readonly TextureBuffer[] _bufferTextureRefs; + + private readonly DescriptorImageInfo[] _textures; + private readonly BufferView[] _bufferTextures; + + private HashSet _storages; + + private int _cachedCommandBufferIndex; + private int _cachedSubmissionCount; + + private readonly bool _isBuffer; + + public bool Bound; + + public ImageArray(VulkanRenderer gd, int size, bool isBuffer) + { + _gd = gd; + + if (isBuffer) + { + _bufferTextureRefs = new TextureBuffer[size]; + _bufferTextures = new BufferView[size]; + } + else + { + _textureRefs = new TextureRef[size]; + _textures = new DescriptorImageInfo[size]; + } + + _storages = null; + + _cachedCommandBufferIndex = -1; + _cachedSubmissionCount = 0; + + _isBuffer = isBuffer; + } + + public void SetFormats(int index, GAL.Format[] imageFormats) + { + for (int i = 0; i < imageFormats.Length; i++) + { + _textureRefs[index + i].ImageFormat = imageFormats[i]; + } + + SetDirty(); + } + + public void SetImages(int index, ITexture[] images) + { + for (int i = 0; i < images.Length; i++) + { + ITexture image = images[i]; + + if (image is TextureBuffer textureBuffer) + { + _bufferTextureRefs[index + i] = textureBuffer; + } + else if (image is TextureView view) + { + _textureRefs[index + i].Storage = view.Storage; + _textureRefs[index + i].View = view; + } + else if (!_isBuffer) + { + _textureRefs[index + i].Storage = null; + _textureRefs[index + i].View = default; + } + else + { + _bufferTextureRefs[index + i] = null; + } + } + + SetDirty(); + } + + private void SetDirty() + { + _cachedCommandBufferIndex = -1; + _storages = null; + + _gd.PipelineInternal.ForceImageDirty(); + } + + public void QueueWriteToReadBarriers(CommandBufferScoped cbs, PipelineStageFlags stageFlags) + { + HashSet storages = _storages; + + if (storages == null) + { + storages = new HashSet(); + + for (int index = 0; index < _textureRefs.Length; index++) + { + if (_textureRefs[index].Storage != null) + { + storages.Add(_textureRefs[index].Storage); + } + } + + _storages = storages; + } + + foreach (TextureStorage storage in storages) + { + storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stageFlags); + } + } + + public ReadOnlySpan GetImageInfos(VulkanRenderer gd, CommandBufferScoped cbs, TextureView dummyTexture) + { + int submissionCount = gd.CommandBufferPool.GetSubmissionCount(cbs.CommandBufferIndex); + + Span textures = _textures; + + if (cbs.CommandBufferIndex == _cachedCommandBufferIndex && submissionCount == _cachedSubmissionCount) + { + return textures; + } + + _cachedCommandBufferIndex = cbs.CommandBufferIndex; + _cachedSubmissionCount = submissionCount; + + for (int i = 0; i < textures.Length; i++) + { + ref var texture = ref textures[i]; + ref var refs = ref _textureRefs[i]; + + if (i > 0 && _textureRefs[i - 1].View == refs.View && _textureRefs[i - 1].ImageFormat == refs.ImageFormat) + { + texture = textures[i - 1]; + + continue; + } + + texture.ImageLayout = ImageLayout.General; + texture.ImageView = refs.View?.GetView(refs.ImageFormat).GetIdentityImageView().Get(cbs).Value ?? default; + + if (texture.ImageView.Handle == 0) + { + texture.ImageView = dummyTexture.GetImageView().Get(cbs).Value; + } + } + + return textures; + } + + public ReadOnlySpan GetBufferViews(CommandBufferScoped cbs) + { + Span bufferTextures = _bufferTextures; + + for (int i = 0; i < bufferTextures.Length; i++) + { + bufferTextures[i] = _bufferTextureRefs[i]?.GetBufferView(cbs, _textureRefs[i].ImageFormat, true) ?? default; + } + + return bufferTextures; + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs index d5169a6884..41ab84d941 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs @@ -898,6 +898,11 @@ namespace Ryujinx.Graphics.Vulkan _descriptorSetUpdater.SetImage(binding, image); } + public void SetImageArray(ShaderStage stage, int binding, IImageArray array) + { + _descriptorSetUpdater.SetImageArray(Cbs, stage, binding, array); + } + public void SetIndexBuffer(BufferRange buffer, IndexType type) { if (buffer.Handle != BufferHandle.Null) @@ -1146,6 +1151,11 @@ namespace Ryujinx.Graphics.Vulkan _descriptorSetUpdater.SetTextureAndSamplerIdentitySwizzle(Cbs, stage, binding, texture, sampler); } + public void SetTextureArray(ShaderStage stage, int binding, ITextureArray array) + { + _descriptorSetUpdater.SetTextureArray(Cbs, stage, binding, array); + } + public void SetTransformFeedbackBuffers(ReadOnlySpan buffers) { PauseTransformFeedbackInternal(); @@ -1375,6 +1385,16 @@ namespace Ryujinx.Graphics.Vulkan SignalCommandBufferChange(); } + public void ForceTextureDirty() + { + _descriptorSetUpdater.ForceTextureDirty(); + } + + public void ForceImageDirty() + { + _descriptorSetUpdater.ForceImageDirty(); + } + public unsafe void TextureBarrier() { MemoryBarrier memoryBarrier = new() diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs index f388d9e88f..fb1f0a5ff6 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs @@ -8,14 +8,7 @@ namespace Ryujinx.Graphics.Vulkan { class PipelineLayoutCacheEntry { - // Those were adjusted based on current descriptor usage and the descriptor counts usually used on pipeline layouts. - // It might be a good idea to tweak them again if those change, or maybe find a way to calculate an optimal value dynamically. - private const uint DefaultUniformBufferPoolCapacity = 19 * DescriptorSetManager.MaxSets; - private const uint DefaultStorageBufferPoolCapacity = 16 * DescriptorSetManager.MaxSets; - private const uint DefaultTexturePoolCapacity = 128 * DescriptorSetManager.MaxSets; - private const uint DefaultImagePoolCapacity = 8 * DescriptorSetManager.MaxSets; - - private const int MaxPoolSizesPerSet = 2; + private const int MaxPoolSizesPerSet = 8; private readonly VulkanRenderer _gd; private readonly Device _device; @@ -24,6 +17,9 @@ namespace Ryujinx.Graphics.Vulkan public PipelineLayout PipelineLayout { get; } private readonly int[] _consumedDescriptorsPerSet; + private readonly DescriptorPoolSize[][] _poolSizes; + + private readonly DescriptorSetManager _descriptorSetManager; private readonly List>[][] _dsCache; private List>[] _currentDsCache; @@ -65,6 +61,9 @@ namespace Ryujinx.Graphics.Vulkan (DescriptorSetLayouts, PipelineLayout) = PipelineLayoutFactory.Create(gd, device, setDescriptors, usePushDescriptors); _consumedDescriptorsPerSet = new int[setDescriptors.Count]; + _poolSizes = new DescriptorPoolSize[setDescriptors.Count][]; + + Span poolSizes = stackalloc DescriptorPoolSize[MaxPoolSizesPerSet]; for (int setIndex = 0; setIndex < setDescriptors.Count; setIndex++) { @@ -76,6 +75,7 @@ namespace Ryujinx.Graphics.Vulkan } _consumedDescriptorsPerSet[setIndex] = count; + _poolSizes[setIndex] = GetDescriptorPoolSizes(poolSizes, setDescriptors[setIndex], DescriptorSetManager.MaxSets).ToArray(); } if (usePushDescriptors) @@ -83,6 +83,8 @@ namespace Ryujinx.Graphics.Vulkan _pdDescriptors = setDescriptors[0]; _pdTemplates = new(); } + + _descriptorSetManager = new DescriptorSetManager(_device, setDescriptors.Count); } public void UpdateCommandBufferIndex(int commandBufferIndex) @@ -105,17 +107,12 @@ namespace Ryujinx.Graphics.Vulkan int index = _dsCacheCursor[setIndex]++; if (index == list.Count) { - Span poolSizes = stackalloc DescriptorPoolSize[MaxPoolSizesPerSet]; - poolSizes = GetDescriptorPoolSizes(poolSizes, setIndex); - - int consumedDescriptors = _consumedDescriptorsPerSet[setIndex]; - - var dsc = _gd.DescriptorSetManager.AllocateDescriptorSet( + var dsc = _descriptorSetManager.AllocateDescriptorSet( _gd.Api, DescriptorSetLayouts[setIndex], - poolSizes, + _poolSizes[setIndex], setIndex, - consumedDescriptors, + _consumedDescriptorsPerSet[setIndex], false); list.Add(dsc); @@ -127,28 +124,35 @@ namespace Ryujinx.Graphics.Vulkan return list[index]; } - private static Span GetDescriptorPoolSizes(Span output, int setIndex) + private static Span GetDescriptorPoolSizes(Span output, ResourceDescriptorCollection setDescriptor, uint multiplier) { - int count = 1; + int count = 0; - switch (setIndex) + for (int index = 0; index < setDescriptor.Descriptors.Count; index++) { - case PipelineBase.UniformSetIndex: - output[0] = new(DescriptorType.UniformBuffer, DefaultUniformBufferPoolCapacity); - break; - case PipelineBase.StorageSetIndex: - output[0] = new(DescriptorType.StorageBuffer, DefaultStorageBufferPoolCapacity); - break; - case PipelineBase.TextureSetIndex: - output[0] = new(DescriptorType.CombinedImageSampler, DefaultTexturePoolCapacity); - output[1] = new(DescriptorType.UniformTexelBuffer, DefaultTexturePoolCapacity); - count = 2; - break; - case PipelineBase.ImageSetIndex: - output[0] = new(DescriptorType.StorageImage, DefaultImagePoolCapacity); - output[1] = new(DescriptorType.StorageTexelBuffer, DefaultImagePoolCapacity); - count = 2; - break; + ResourceDescriptor descriptor = setDescriptor.Descriptors[index]; + DescriptorType descriptorType = descriptor.Type.Convert(); + + bool found = false; + + for (int poolSizeIndex = 0; poolSizeIndex < count; poolSizeIndex++) + { + if (output[poolSizeIndex].Type == descriptorType) + { + output[poolSizeIndex].DescriptorCount += (uint)descriptor.Count * multiplier; + found = true; + break; + } + } + + if (!found) + { + output[count++] = new DescriptorPoolSize() + { + Type = descriptorType, + DescriptorCount = (uint)descriptor.Count, + }; + } } return output[..count]; @@ -206,6 +210,8 @@ namespace Ryujinx.Graphics.Vulkan { _gd.Api.DestroyDescriptorSetLayout(_device, DescriptorSetLayouts[i], null); } + + _descriptorSetManager.Dispose(); } } diff --git a/src/Ryujinx.Graphics.Vulkan/ResourceBindingSegment.cs b/src/Ryujinx.Graphics.Vulkan/ResourceBindingSegment.cs index 8902f13e66..6e27da4a68 100644 --- a/src/Ryujinx.Graphics.Vulkan/ResourceBindingSegment.cs +++ b/src/Ryujinx.Graphics.Vulkan/ResourceBindingSegment.cs @@ -8,13 +8,15 @@ namespace Ryujinx.Graphics.Vulkan public readonly int Count; public readonly ResourceType Type; public readonly ResourceStages Stages; + public readonly bool IsArray; - public ResourceBindingSegment(int binding, int count, ResourceType type, ResourceStages stages) + public ResourceBindingSegment(int binding, int count, ResourceType type, ResourceStages stages, bool isArray) { Binding = binding; Count = count; Type = type; Stages = stages; + IsArray = isArray; } } } diff --git a/src/Ryujinx.Graphics.Vulkan/ResourceLayoutBuilder.cs b/src/Ryujinx.Graphics.Vulkan/ResourceLayoutBuilder.cs index f5ac39684f..76a5ef4f95 100644 --- a/src/Ryujinx.Graphics.Vulkan/ResourceLayoutBuilder.cs +++ b/src/Ryujinx.Graphics.Vulkan/ResourceLayoutBuilder.cs @@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Vulkan }; _resourceDescriptors[setIndex].Add(new ResourceDescriptor(binding, 1, type, stages)); - _resourceUsages[setIndex].Add(new ResourceUsage(binding, type, stages)); + _resourceUsages[setIndex].Add(new ResourceUsage(binding, 1, type, stages)); return this; } diff --git a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs index b2be541bf7..1785469839 100644 --- a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs +++ b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs @@ -241,7 +241,9 @@ namespace Ryujinx.Graphics.Vulkan if (currentDescriptor.Binding + currentCount != descriptor.Binding || currentDescriptor.Type != descriptor.Type || - currentDescriptor.Stages != descriptor.Stages) + currentDescriptor.Stages != descriptor.Stages || + currentDescriptor.Count > 1 || + descriptor.Count > 1) { if (currentCount != 0) { @@ -249,7 +251,8 @@ namespace Ryujinx.Graphics.Vulkan currentDescriptor.Binding, currentCount, currentDescriptor.Type, - currentDescriptor.Stages)); + currentDescriptor.Stages, + currentDescriptor.Count > 1)); } currentDescriptor = descriptor; @@ -267,7 +270,8 @@ namespace Ryujinx.Graphics.Vulkan currentDescriptor.Binding, currentCount, currentDescriptor.Type, - currentDescriptor.Stages)); + currentDescriptor.Stages, + currentDescriptor.Count > 1)); } segments[setIndex] = currentSegments.ToArray(); @@ -293,7 +297,9 @@ namespace Ryujinx.Graphics.Vulkan if (currentUsage.Binding + currentCount != usage.Binding || currentUsage.Type != usage.Type || - currentUsage.Stages != usage.Stages) + currentUsage.Stages != usage.Stages || + currentUsage.ArrayLength > 1 || + usage.ArrayLength > 1) { if (currentCount != 0) { @@ -301,11 +307,12 @@ namespace Ryujinx.Graphics.Vulkan currentUsage.Binding, currentCount, currentUsage.Type, - currentUsage.Stages)); + currentUsage.Stages, + currentUsage.ArrayLength > 1)); } currentUsage = usage; - currentCount = 1; + currentCount = usage.ArrayLength; } else { @@ -319,7 +326,8 @@ namespace Ryujinx.Graphics.Vulkan currentUsage.Binding, currentCount, currentUsage.Type, - currentUsage.Stages)); + currentUsage.Stages, + currentUsage.ArrayLength > 1)); } segments[setIndex] = currentSegments.ToArray(); @@ -344,7 +352,13 @@ namespace Ryujinx.Graphics.Vulkan if (segments != null && segments.Length > 0) { - templates[setIndex] = new DescriptorSetTemplate(_gd, _device, segments, _plce, IsCompute ? PipelineBindPoint.Compute : PipelineBindPoint.Graphics, setIndex); + templates[setIndex] = new DescriptorSetTemplate( + _gd, + _device, + segments, + _plce, + IsCompute ? PipelineBindPoint.Compute : PipelineBindPoint.Graphics, + setIndex); } } diff --git a/src/Ryujinx.Graphics.Vulkan/TextureArray.cs b/src/Ryujinx.Graphics.Vulkan/TextureArray.cs new file mode 100644 index 0000000000..6ef9087bcd --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/TextureArray.cs @@ -0,0 +1,194 @@ +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Vulkan +{ + class TextureArray : ITextureArray + { + private readonly VulkanRenderer _gd; + + private struct TextureRef + { + public TextureStorage Storage; + public Auto View; + public Auto Sampler; + } + + private readonly TextureRef[] _textureRefs; + private readonly TextureBuffer[] _bufferTextureRefs; + + private readonly DescriptorImageInfo[] _textures; + private readonly BufferView[] _bufferTextures; + + private HashSet _storages; + + private int _cachedCommandBufferIndex; + private int _cachedSubmissionCount; + + private readonly bool _isBuffer; + + public bool Bound; + + public TextureArray(VulkanRenderer gd, int size, bool isBuffer) + { + _gd = gd; + + if (isBuffer) + { + _bufferTextureRefs = new TextureBuffer[size]; + _bufferTextures = new BufferView[size]; + } + else + { + _textureRefs = new TextureRef[size]; + _textures = new DescriptorImageInfo[size]; + } + + _storages = null; + + _cachedCommandBufferIndex = -1; + _cachedSubmissionCount = 0; + + _isBuffer = isBuffer; + } + + public void SetSamplers(int index, ISampler[] samplers) + { + for (int i = 0; i < samplers.Length; i++) + { + ISampler sampler = samplers[i]; + + if (sampler is SamplerHolder samplerHolder) + { + _textureRefs[index + i].Sampler = samplerHolder.GetSampler(); + } + else + { + _textureRefs[index + i].Sampler = default; + } + } + + SetDirty(); + } + + public void SetTextures(int index, ITexture[] textures) + { + for (int i = 0; i < textures.Length; i++) + { + ITexture texture = textures[i]; + + if (texture is TextureBuffer textureBuffer) + { + _bufferTextureRefs[index + i] = textureBuffer; + } + else if (texture is TextureView view) + { + _textureRefs[index + i].Storage = view.Storage; + _textureRefs[index + i].View = view.GetImageView(); + } + else if (!_isBuffer) + { + _textureRefs[index + i].Storage = null; + _textureRefs[index + i].View = default; + } + else + { + _bufferTextureRefs[index + i] = null; + } + } + + SetDirty(); + } + + private void SetDirty() + { + _cachedCommandBufferIndex = -1; + _storages = null; + + _gd.PipelineInternal.ForceTextureDirty(); + } + + public void QueueWriteToReadBarriers(CommandBufferScoped cbs, PipelineStageFlags stageFlags) + { + HashSet storages = _storages; + + if (storages == null) + { + storages = new HashSet(); + + for (int index = 0; index < _textureRefs.Length; index++) + { + if (_textureRefs[index].Storage != null) + { + storages.Add(_textureRefs[index].Storage); + } + } + + _storages = storages; + } + + foreach (TextureStorage storage in storages) + { + storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stageFlags); + } + } + + public ReadOnlySpan GetImageInfos(VulkanRenderer gd, CommandBufferScoped cbs, TextureView dummyTexture, SamplerHolder dummySampler) + { + int submissionCount = gd.CommandBufferPool.GetSubmissionCount(cbs.CommandBufferIndex); + + Span textures = _textures; + + if (cbs.CommandBufferIndex == _cachedCommandBufferIndex && submissionCount == _cachedSubmissionCount) + { + return textures; + } + + _cachedCommandBufferIndex = cbs.CommandBufferIndex; + _cachedSubmissionCount = submissionCount; + + for (int i = 0; i < textures.Length; i++) + { + ref var texture = ref textures[i]; + ref var refs = ref _textureRefs[i]; + + if (i > 0 && _textureRefs[i - 1].View == refs.View && _textureRefs[i - 1].Sampler == refs.Sampler) + { + texture = textures[i - 1]; + + continue; + } + + texture.ImageLayout = ImageLayout.General; + texture.ImageView = refs.View?.Get(cbs).Value ?? default; + texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default; + + if (texture.ImageView.Handle == 0) + { + texture.ImageView = dummyTexture.GetImageView().Get(cbs).Value; + } + + if (texture.Sampler.Handle == 0) + { + texture.Sampler = dummySampler.GetSampler().Get(cbs).Value; + } + } + + return textures; + } + + public ReadOnlySpan GetBufferViews(CommandBufferScoped cbs) + { + Span bufferTextures = _bufferTextures; + + for (int i = 0; i < bufferTextures.Length; i++) + { + bufferTextures[i] = _bufferTextureRefs[i]?.GetBufferView(cbs, false) ?? default; + } + + return bufferTextures; + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index d1afeaeaed..e75e7f4b4c 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -48,7 +48,6 @@ namespace Ryujinx.Graphics.Vulkan internal MemoryAllocator MemoryAllocator { get; private set; } internal HostMemoryAllocator HostMemoryAllocator { get; private set; } internal CommandBufferPool CommandBufferPool { get; private set; } - internal DescriptorSetManager DescriptorSetManager { get; private set; } internal PipelineLayoutCache PipelineLayoutCache { get; private set; } internal BackgroundResources BackgroundResources { get; private set; } internal Action InterruptAction { get; private set; } @@ -414,8 +413,6 @@ namespace Ryujinx.Graphics.Vulkan CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex); - DescriptorSetManager = new DescriptorSetManager(_device, PipelineBase.DescriptorSetLayouts); - PipelineLayoutCache = new PipelineLayoutCache(); BackgroundResources = new BackgroundResources(this, _device); @@ -507,6 +504,11 @@ namespace Ryujinx.Graphics.Vulkan return BufferManager.CreateSparse(this, storageBuffers); } + public IImageArray CreateImageArray(int size, bool isBuffer) + { + return new ImageArray(this, size, isBuffer); + } + public IProgram CreateProgram(ShaderSource[] sources, ShaderInfo info) { bool isCompute = sources.Length == 1 && sources[0].Stage == ShaderStage.Compute; @@ -539,6 +541,11 @@ namespace Ryujinx.Graphics.Vulkan return CreateTextureView(info); } + public ITextureArray CreateTextureArray(int size, bool isBuffer) + { + return new TextureArray(this, size, isBuffer); + } + internal TextureView CreateTextureView(TextureCreateInfo info) { // This should be disposed when all views are destroyed. @@ -925,7 +932,6 @@ namespace Ryujinx.Graphics.Vulkan HelperShader.Dispose(); _pipeline.Dispose(); BufferManager.Dispose(); - DescriptorSetManager.Dispose(); PipelineLayoutCache.Dispose(); Barriers.Dispose(); diff --git a/src/Ryujinx.ShaderTools/Program.cs b/src/Ryujinx.ShaderTools/Program.cs index 04453912b3..4252f1b258 100644 --- a/src/Ryujinx.ShaderTools/Program.cs +++ b/src/Ryujinx.ShaderTools/Program.cs @@ -11,17 +11,57 @@ namespace Ryujinx.ShaderTools { private class GpuAccessor : IGpuAccessor { + private const int DefaultArrayLength = 32; + private readonly byte[] _data; + private int _texturesCount; + private int _imagesCount; + public GpuAccessor(byte[] data) { _data = data; + _texturesCount = 0; + _imagesCount = 0; + } + + public int CreateConstantBufferBinding(int index) + { + return index + 1; + } + + public int CreateImageBinding(int count, bool isBuffer) + { + int binding = _imagesCount; + + _imagesCount += count; + + return binding; + } + + public int CreateStorageBufferBinding(int index) + { + return index; + } + + public int CreateTextureBinding(int count, bool isBuffer) + { + int binding = _texturesCount; + + _texturesCount += count; + + return binding; } public ReadOnlySpan GetCode(ulong address, int minimumSize) { return MemoryMarshal.Cast(new ReadOnlySpan(_data)[(int)address..]); } + + public int QueryTextureArrayLengthFromBuffer(int slot) + { + return DefaultArrayLength; + } } private class Options From c1b0ab805a3f811836b00f3577ac2768998f6746 Mon Sep 17 00:00:00 2001 From: MutantAura <44103205+MutantAura@users.noreply.github.com> Date: Mon, 8 Apr 2024 00:58:05 +0100 Subject: [PATCH 40/87] Disable CLI setting persistence for HW-accelerated GUI. (#6620) --- src/Ryujinx.UI.Common/Helper/CommandLineState.cs | 3 --- src/Ryujinx/Program.cs | 9 ++++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Ryujinx.UI.Common/Helper/CommandLineState.cs b/src/Ryujinx.UI.Common/Helper/CommandLineState.cs index 6de963a74a..bbacd5fec3 100644 --- a/src/Ryujinx.UI.Common/Helper/CommandLineState.cs +++ b/src/Ryujinx.UI.Common/Helper/CommandLineState.cs @@ -91,9 +91,6 @@ namespace Ryujinx.UI.Common.Helper case "--software-gui": OverrideHardwareAcceleration = false; break; - case "--hardware-gui": - OverrideHardwareAcceleration = true; - break; default: LaunchPathArg = arg; break; diff --git a/src/Ryujinx/Program.cs b/src/Ryujinx/Program.cs index 4a30aee9ca..89e895e81b 100644 --- a/src/Ryujinx/Program.cs +++ b/src/Ryujinx/Program.cs @@ -27,6 +27,7 @@ namespace Ryujinx.Ava public static string Version { get; private set; } public static string ConfigurationPath { get; private set; } public static bool PreviewerDetached { get; private set; } + public static bool UseHardwareAcceleration { get; private set; } [LibraryImport("user32.dll", SetLastError = true)] public static partial int MessageBoxA(IntPtr hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type); @@ -60,14 +61,14 @@ namespace Ryujinx.Ava EnableMultiTouch = true, EnableIme = true, EnableInputFocusProxy = Environment.GetEnvironmentVariable("XDG_CURRENT_DESKTOP") == "gamescope", - RenderingMode = ConfigurationState.Instance.EnableHardwareAcceleration ? + RenderingMode = UseHardwareAcceleration ? new[] { X11RenderingMode.Glx, X11RenderingMode.Software } : new[] { X11RenderingMode.Software }, }) .With(new Win32PlatformOptions { WinUICompositionBackdropCornerRadius = 8.0f, - RenderingMode = ConfigurationState.Instance.EnableHardwareAcceleration ? + RenderingMode = UseHardwareAcceleration ? new[] { Win32RenderingMode.AngleEgl, Win32RenderingMode.Software } : new[] { Win32RenderingMode.Software }, }) @@ -165,6 +166,8 @@ namespace Ryujinx.Ava } } + UseHardwareAcceleration = ConfigurationState.Instance.EnableHardwareAcceleration.Value; + // Check if graphics backend was overridden if (CommandLineState.OverrideGraphicsBackend != null) { @@ -199,7 +202,7 @@ namespace Ryujinx.Ava // Check if hardware-acceleration was overridden. if (CommandLineState.OverrideHardwareAcceleration != null) { - ConfigurationState.Instance.EnableHardwareAcceleration.Value = CommandLineState.OverrideHardwareAcceleration.Value; + UseHardwareAcceleration = CommandLineState.OverrideHardwareAcceleration.Value; } } From 7a971edb577c64b178ef7a82de3e9e34ba74755c Mon Sep 17 00:00:00 2001 From: gdkchan Date: Mon, 8 Apr 2024 20:26:47 -0300 Subject: [PATCH 41/87] Pin audio renderer update output buffers (#6633) --- src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs index 54de072105..4d446bba76 100644 --- a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs @@ -61,6 +61,9 @@ namespace Ryujinx.Horizon.Sdk.Audio.Detail [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Memory performanceOutput, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySequence input) { + using MemoryHandle outputHandle = output.Pin(); + using MemoryHandle performanceOutputHandle = performanceOutput.Pin(); + Result result = new Result((int)_renderSystem.Update(output, performanceOutput, input)); return result; From 80201466ae097fafb1b2a4b32dde98ce3ed3932c Mon Sep 17 00:00:00 2001 From: gdkchan Date: Tue, 9 Apr 2024 14:03:55 -0300 Subject: [PATCH 42/87] Fast D32S8 2D depth texture copy (#6636) --- src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs index 5b930fa47a..3cdeac9c5c 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs @@ -247,6 +247,10 @@ namespace Ryujinx.Graphics.Gpu.Image { return TextureMatchQuality.FormatAlias; } + else if (lhs.FormatInfo.Format == Format.D32FloatS8Uint && rhs.FormatInfo.Format == Format.R32G32Float) + { + return TextureMatchQuality.FormatAlias; + } } return lhs.FormatInfo.Format == rhs.FormatInfo.Format ? TextureMatchQuality.Perfect : TextureMatchQuality.NoMatch; From 338ff79e1e962250110047be781e069b4dccba01 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Tue, 9 Apr 2024 14:24:46 -0300 Subject: [PATCH 43/87] Use ResScaleUnsupported flag for texture arrays (#6626) --- .../Image/TextureBindingsArrayCache.cs | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs index 70ea1f6b91..4645317c4f 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs @@ -224,7 +224,8 @@ namespace Ryujinx.Graphics.Gpu.Image /// Synchronizes memory for all textures in the array. /// /// Indicates if the texture may be modified by the access - public void SynchronizeMemory(bool isStore) + /// Indicates if the texture should be blacklisted for scaling + public void SynchronizeMemory(bool isStore, bool blacklistScale) { foreach (Texture texture in Textures.Keys) { @@ -234,6 +235,13 @@ namespace Ryujinx.Graphics.Gpu.Image { texture.SignalModified(); } + + if (blacklistScale && texture.ScaleMode != TextureScaleMode.Blacklisted) + { + // Scaling textures used on arrays is currently not supported. + + texture.BlacklistScale(); + } } } @@ -467,6 +475,7 @@ namespace Ryujinx.Graphics.Gpu.Image bool poolsModified = entry.PoolsModified(); bool isStore = bindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore); + bool resScaleUnsupported = bindingInfo.Flags.HasFlag(TextureUsageFlags.ResScaleUnsupported); ReadOnlySpan cachedTextureBuffer; ReadOnlySpan cachedSamplerBuffer; @@ -475,7 +484,7 @@ namespace Ryujinx.Graphics.Gpu.Image { if (entry.MatchesSequenceNumber(_context.SequenceNumber)) { - entry.SynchronizeMemory(isStore); + entry.SynchronizeMemory(isStore, resScaleUnsupported); if (isImage) { @@ -504,7 +513,7 @@ namespace Ryujinx.Graphics.Gpu.Image if (entry.MatchesBufferData(cachedTextureBuffer, cachedSamplerBuffer, separateSamplerBuffer, samplerWordOffset)) { - entry.SynchronizeMemory(isStore); + entry.SynchronizeMemory(isStore, resScaleUnsupported); if (isImage) { @@ -569,6 +578,13 @@ namespace Ryujinx.Graphics.Gpu.Image { texture.SignalModified(); } + + if (resScaleUnsupported && texture.ScaleMode != TextureScaleMode.Blacklisted) + { + // Scaling textures used on arrays is currently not supported. + + texture.BlacklistScale(); + } } Sampler sampler = samplerPool?.Get(samplerId); From 543d75a587ee2197b83762dba393c1d525c601fc Mon Sep 17 00:00:00 2001 From: gdkchan Date: Tue, 9 Apr 2024 14:34:14 -0300 Subject: [PATCH 44/87] CPU: Produce non-inf results for RSQRTE instruction with subnormal inputs (#6634) * CPU: Produce non-inf results for RSQRTE instruction with subnormal inputs * PPTC version bump --- .../Instructions/InstEmitSimdArithmetic.cs | 12 ++++++++++-- src/ARMeilleure/Translation/PTC/Ptc.cs | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs b/src/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs index 543aab0236..13d9fac683 100644 --- a/src/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs +++ b/src/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs @@ -2426,7 +2426,11 @@ namespace ARMeilleure.Instructions } else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0) { - Operand res = EmitSse41Round32Exp8OpF(context, context.AddIntrinsic(Intrinsic.X86Rsqrtss, GetVec(op.Rn)), scalar: true); + // RSQRTSS handles subnormals as zero, which differs from Arm, so we can't use it here. + + Operand res = context.AddIntrinsic(Intrinsic.X86Sqrtss, GetVec(op.Rn)); + res = context.AddIntrinsic(Intrinsic.X86Rcpss, res); + res = EmitSse41Round32Exp8OpF(context, res, scalar: true); context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); } @@ -2451,7 +2455,11 @@ namespace ARMeilleure.Instructions } else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0) { - Operand res = EmitSse41Round32Exp8OpF(context, context.AddIntrinsic(Intrinsic.X86Rsqrtps, GetVec(op.Rn)), scalar: false); + // RSQRTPS handles subnormals as zero, which differs from Arm, so we can't use it here. + + Operand res = context.AddIntrinsic(Intrinsic.X86Sqrtps, GetVec(op.Rn)); + res = context.AddIntrinsic(Intrinsic.X86Rcpps, res); + res = EmitSse41Round32Exp8OpF(context, res, scalar: false); if (op.RegisterSize == RegisterSize.Simd64) { diff --git a/src/ARMeilleure/Translation/PTC/Ptc.cs b/src/ARMeilleure/Translation/PTC/Ptc.cs index f987284fa1..58f065342e 100644 --- a/src/ARMeilleure/Translation/PTC/Ptc.cs +++ b/src/ARMeilleure/Translation/PTC/Ptc.cs @@ -29,7 +29,7 @@ namespace ARMeilleure.Translation.PTC private const string OuterHeaderMagicString = "PTCohd\0\0"; private const string InnerHeaderMagicString = "PTCihd\0\0"; - private const uint InternalVersion = 6613; //! To be incremented manually for each change to the ARMeilleure project. + private const uint InternalVersion = 6634; //! To be incremented manually for each change to the ARMeilleure project. private const string ActualDir = "0"; private const string BackupDir = "1"; From e7f2342ebaa0aa143db0c41ce841ed501b0260d5 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Wed, 10 Apr 2024 12:07:31 -0300 Subject: [PATCH 45/87] Fix input consumed by audio renderer SplitterState.Update (#6640) * Fix input consumed by audio renderer SplitterState.Update * Use sizeof(int) to make clear what the value is --- src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs index 109c81b23a..944f092d2c 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs @@ -159,6 +159,11 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter } } + if (destinationCount < parameter.DestinationCount) + { + input.Advance((parameter.DestinationCount - destinationCount) * sizeof(int)); + } + Debug.Assert(parameter.Id == Id); if (parameter.Id == Id) From 0652813b0f7500bf07309a6906331644c3a2363b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Apr 2024 23:30:17 +0200 Subject: [PATCH 46/87] nuget: bump Microsoft.IdentityModel.JsonWebTokens from 7.4.0 to 7.5.1 (#6627) Bumps [Microsoft.IdentityModel.JsonWebTokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 7.4.0 to 7.5.1. - [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases) - [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md) - [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/v7.4.0...7.5.1) --- updated-dependencies: - dependency-name: Microsoft.IdentityModel.JsonWebTokens dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 5d6adcb367..c3af18ceec 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -20,7 +20,7 @@ - + From 9480e5c5ceeac3219dc0e0a90c0cb2a5487a7298 Mon Sep 17 00:00:00 2001 From: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> Date: Wed, 10 Apr 2024 22:40:17 +0100 Subject: [PATCH 47/87] Ava UI: Prevent Status Bar Backend Update (#6506) * Prevent Status Bar Backend Update * Make it a switch --- src/Ryujinx/AppHost.cs | 18 +++++++++++++++--- src/Ryujinx/UI/Models/StatusInitEventArgs.cs | 16 ++++++++++++++++ .../UI/Models/StatusUpdatedEventArgs.cs | 6 +----- .../UI/ViewModels/MainWindowViewModel.cs | 15 +++++++++++++-- 4 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 src/Ryujinx/UI/Models/StatusInitEventArgs.cs diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs index d69bfc147f..43e7a79ebb 100644 --- a/src/Ryujinx/AppHost.cs +++ b/src/Ryujinx/AppHost.cs @@ -112,6 +112,7 @@ namespace Ryujinx.Ava private readonly object _lockObject = new(); public event EventHandler AppExit; + public event EventHandler StatusInitEvent; public event EventHandler StatusUpdatedEvent; public VirtualFileSystem VirtualFileSystem { get; } @@ -947,6 +948,7 @@ namespace Ryujinx.Ava { _renderingStarted = true; _viewModel.SwitchToRenderer(false); + InitStatus(); } Device.PresentFrame(() => (RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.SwapBuffers()); @@ -970,6 +972,18 @@ namespace Ryujinx.Ava (RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(true); } + public void InitStatus() + { + StatusInitEvent?.Invoke(this, new StatusInitEventArgs( + ConfigurationState.Instance.Graphics.GraphicsBackend.Value switch + { + GraphicsBackend.Vulkan => "Vulkan", + GraphicsBackend.OpenGl => "OpenGL", + _ => throw new NotImplementedException() + }, + $"GPU: {_renderer.GetHardwareInfo().GpuDriver}")); + } + public void UpdateStatus() { // 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. @@ -983,12 +997,10 @@ namespace Ryujinx.Ava StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs( Device.EnableDeviceVsync, LocaleManager.Instance[LocaleKeys.VolumeShort] + $": {(int)(Device.GetVolume() * 100)}%", - ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan ? "Vulkan" : "OpenGL", dockedMode, ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(), LocaleManager.Instance[LocaleKeys.Game] + $": {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)", - $"FIFO: {Device.Statistics.GetFifoPercent():00.00} %", - $"GPU: {_renderer.GetHardwareInfo().GpuDriver}")); + $"FIFO: {Device.Statistics.GetFifoPercent():00.00} %")); } public async Task ShowExitPrompt() diff --git a/src/Ryujinx/UI/Models/StatusInitEventArgs.cs b/src/Ryujinx/UI/Models/StatusInitEventArgs.cs new file mode 100644 index 0000000000..4b08737e96 --- /dev/null +++ b/src/Ryujinx/UI/Models/StatusInitEventArgs.cs @@ -0,0 +1,16 @@ +using System; + +namespace Ryujinx.Ava.UI.Models +{ + internal class StatusInitEventArgs : EventArgs + { + public string GpuBackend { get; } + public string GpuName { get; } + + public StatusInitEventArgs(string gpuBackend, string gpuName) + { + GpuBackend = gpuBackend; + GpuName = gpuName; + } + } +} diff --git a/src/Ryujinx/UI/Models/StatusUpdatedEventArgs.cs b/src/Ryujinx/UI/Models/StatusUpdatedEventArgs.cs index 7f04c0eed9..ee5648faf8 100644 --- a/src/Ryujinx/UI/Models/StatusUpdatedEventArgs.cs +++ b/src/Ryujinx/UI/Models/StatusUpdatedEventArgs.cs @@ -6,23 +6,19 @@ namespace Ryujinx.Ava.UI.Models { public bool VSyncEnabled { get; } public string VolumeStatus { get; } - public string GpuBackend { get; } public string AspectRatio { get; } public string DockedMode { get; } public string FifoStatus { get; } public string GameStatus { get; } - public string GpuName { get; } - public StatusUpdatedEventArgs(bool vSyncEnabled, string volumeStatus, string gpuBackend, string dockedMode, string aspectRatio, string gameStatus, string fifoStatus, string gpuName) + public StatusUpdatedEventArgs(bool vSyncEnabled, string volumeStatus, string dockedMode, string aspectRatio, string gameStatus, string fifoStatus) { VSyncEnabled = vSyncEnabled; VolumeStatus = volumeStatus; - GpuBackend = gpuBackend; DockedMode = dockedMode; AspectRatio = aspectRatio; GameStatus = gameStatus; FifoStatus = fifoStatus; - GpuName = gpuName; } } } diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index 036a536e5c..130e708c77 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -1172,6 +1172,7 @@ namespace Ryujinx.Ava.UI.ViewModels { RendererHostControl.WindowCreated += RendererHost_Created; + AppHost.StatusInitEvent += Init_StatusBar; AppHost.StatusUpdatedEvent += Update_StatusBar; AppHost.AppExit += AppHost_AppExit; @@ -1198,6 +1199,18 @@ namespace Ryujinx.Ava.UI.ViewModels } } + private void Init_StatusBar(object sender, StatusInitEventArgs args) + { + if (ShowMenuAndStatusBar && !ShowLoadProgress) + { + Dispatcher.UIThread.InvokeAsync(() => + { + GpuNameText = args.GpuName; + BackendText = args.GpuBackend; + }); + } + } + private void Update_StatusBar(object sender, StatusUpdatedEventArgs args) { if (ShowMenuAndStatusBar && !ShowLoadProgress) @@ -1220,8 +1233,6 @@ namespace Ryujinx.Ava.UI.ViewModels GameStatusText = args.GameStatus; VolumeStatusText = args.VolumeStatus; FifoStatusText = args.FifoStatus; - GpuNameText = args.GpuName; - BackendText = args.GpuBackend; ShowStatusSeparator = true; }); From 22e3ff06b51db0fa72e9f2dc2aee395a5d1aa2df Mon Sep 17 00:00:00 2001 From: WilliamWsyHK Date: Thu, 11 Apr 2024 06:03:37 +0800 Subject: [PATCH 48/87] Update StoreConstantToMemory to match StoreConstantToAddress on value read (#6642) --- .../HOS/Tamper/CodeEmitters/StoreConstantToMemory.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreConstantToMemory.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreConstantToMemory.cs index 27a99bb630..09795c9f8a 100644 --- a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreConstantToMemory.cs +++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreConstantToMemory.cs @@ -15,7 +15,8 @@ namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters private const int OffsetRegisterIndex = 6; private const int ValueImmediateIndex = 8; - private const int ValueImmediateSize = 16; + private const int ValueImmediateSize8 = 8; + private const int ValueImmediateSize16 = 16; public static void Emit(byte[] instruction, CompilationContext context) { @@ -31,7 +32,8 @@ namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters Register sourceRegister = context.GetRegister(instruction[AddressRegisterIndex]); byte incrementAddressRegister = instruction[IncrementAddressRegisterIndex]; byte useOffsetRegister = instruction[UseOffsetRegisterIndex]; - ulong immediate = InstructionHelper.GetImmediate(instruction, ValueImmediateIndex, ValueImmediateSize); + int valueImmediateSize = operationWidth <= 4 ? ValueImmediateSize8 : ValueImmediateSize16; + ulong immediate = InstructionHelper.GetImmediate(instruction, ValueImmediateIndex, valueImmediateSize); Value storeValue = new(immediate); Pointer destinationMemory; From a8f7ababb594bd20aebe6192c465559d1b2b8f73 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Wed, 10 Apr 2024 21:50:06 -0300 Subject: [PATCH 49/87] =?UTF-8?q?Revert=20"Update=20StoreConstantToMemory?= =?UTF-8?q?=20to=20match=20StoreConstantToAddress=20on=20value=E2=80=A6"?= =?UTF-8?q?=20(#6649)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 22e3ff06b51db0fa72e9f2dc2aee395a5d1aa2df. --- .../HOS/Tamper/CodeEmitters/StoreConstantToMemory.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreConstantToMemory.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreConstantToMemory.cs index 09795c9f8a..27a99bb630 100644 --- a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreConstantToMemory.cs +++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreConstantToMemory.cs @@ -15,8 +15,7 @@ namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters private const int OffsetRegisterIndex = 6; private const int ValueImmediateIndex = 8; - private const int ValueImmediateSize8 = 8; - private const int ValueImmediateSize16 = 16; + private const int ValueImmediateSize = 16; public static void Emit(byte[] instruction, CompilationContext context) { @@ -32,8 +31,7 @@ namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters Register sourceRegister = context.GetRegister(instruction[AddressRegisterIndex]); byte incrementAddressRegister = instruction[IncrementAddressRegisterIndex]; byte useOffsetRegister = instruction[UseOffsetRegisterIndex]; - int valueImmediateSize = operationWidth <= 4 ? ValueImmediateSize8 : ValueImmediateSize16; - ulong immediate = InstructionHelper.GetImmediate(instruction, ValueImmediateIndex, valueImmediateSize); + ulong immediate = InstructionHelper.GetImmediate(instruction, ValueImmediateIndex, ValueImmediateSize); Value storeValue = new(immediate); Pointer destinationMemory; From 2ddd3dd4a7e03aa14626d3f4336d20b904ef4c3a Mon Sep 17 00:00:00 2001 From: gdkchan Date: Thu, 11 Apr 2024 09:56:21 -0300 Subject: [PATCH 50/87] Allow BSD sockets Poll to exit when emulation ends (#6650) --- src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs index 1e8a900512..21d48288ec 100644 --- a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs @@ -440,8 +440,9 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd // If we are here, that mean nothing was available, sleep for 50ms context.Device.System.KernelContext.Syscall.SleepThread(50 * 1000000); + context.Thread.HandlePostSyscall(); } - while (PerformanceCounter.ElapsedMilliseconds < budgetLeftMilliseconds); + while (context.Thread.Context.Running && PerformanceCounter.ElapsedMilliseconds < budgetLeftMilliseconds); } else if (timeout == -1) { From e916662b0f17b93d8987d481784cd45073335990 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Thu, 11 Apr 2024 17:24:19 -0300 Subject: [PATCH 51/87] Account for swapchain image count change after re-creation (#6652) --- src/Ryujinx.Graphics.Vulkan/Window.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Ryujinx.Graphics.Vulkan/Window.cs b/src/Ryujinx.Graphics.Vulkan/Window.cs index 5ddb6eedae..a4ac9e9f19 100644 --- a/src/Ryujinx.Graphics.Vulkan/Window.cs +++ b/src/Ryujinx.Graphics.Vulkan/Window.cs @@ -330,6 +330,7 @@ namespace Ryujinx.Graphics.Vulkan _swapchainIsDirty) { RecreateSwapchain(); + semaphoreIndex = (_frameIndex - 1) % _imageAvailableSemaphores.Length; } else { From 268c9aecf8e9181bb7114cf1dd826f00b2237714 Mon Sep 17 00:00:00 2001 From: jhorv <38920027+jhorv@users.noreply.github.com> Date: Sun, 14 Apr 2024 16:06:14 -0400 Subject: [PATCH 52/87] Texture loading: reduce memory allocations (#6623) * rebase * add methods Ryyjinx.Common EmbeddedResources and SteamUtils * GAL changes - change SetData() methods and ThreadedTexture commands to use IMemoryOwner instead of SpanOrArray * Ryujinx.Graphics.Texture: change texture conversion methods to return IMemoryOwner and allocate from ByteMemoryPool * Ryujinx.Graphics.OpenGL: update ITexture and Texture-like types with SetData() methods to take IMemoryOwner instead of SpanOrArray * Ryujinx.Graphics.Vulkan: update ITexture and Texture-like types with SetData() methods to take IMemoryOwner instead of SpanOrArray * Ryujinx.Graphics.Gpu: update ITexture and Texture-like types with SetData() methods to take IMemoryOwner instead of SpanOrArray * Remove now-unused SpanOrArray * post-rebase cleanup * PixelConverter: remove unsafe modifier on safe methods, and remove one unnecessary cast * use ByteMemoryPool.Rent() in GetWritableRegion() impls * fix formatting, rename `ReadRentedMemory()` to `ReadFileToRentedMemory()`` * Texture.ConvertToHostCompatibleFormat(): dispose of `result` in Astc decode branch --- src/Ryujinx.Common/Memory/SpanOrArray.cs | 89 --------- .../Utilities/EmbeddedResources.cs | 17 ++ src/Ryujinx.Common/Utilities/StreamUtils.cs | 67 ++++++- .../DeviceMemoryManager.cs | 8 +- src/Ryujinx.Graphics.GAL/ITexture.cs | 32 +++- .../Commands/Texture/TextureSetDataCommand.cs | 8 +- .../Texture/TextureSetDataSliceCommand.cs | 8 +- .../TextureSetDataSliceRegionCommand.cs | 8 +- .../Resources/ThreadedTexture.cs | 17 +- .../Engine/Dma/DmaClass.cs | 3 +- .../InlineToMemory/InlineToMemoryClass.cs | 4 +- src/Ryujinx.Graphics.Gpu/Image/Texture.cs | 171 +++++++++++------- .../Image/TextureGroup.cs | 4 +- .../Memory/MemoryManager.cs | 8 +- .../Memory/PhysicalMemory.cs | 8 +- .../Effects/SmaaPostProcessingEffect.cs | 7 +- .../Image/FormatConverter.cs | 10 +- .../Image/TextureBuffer.cs | 15 +- .../Image/TextureView.cs | 94 +++++----- .../Astc/AstcDecoder.cs | 10 +- src/Ryujinx.Graphics.Texture/BCnDecoder.cs | 46 ++--- src/Ryujinx.Graphics.Texture/BCnEncoder.cs | 11 +- src/Ryujinx.Graphics.Texture/ETC2Decoder.cs | 20 +- .../LayoutConverter.cs | 15 +- .../PixelConverter.cs | 36 ++-- .../DescriptorSetUpdater.cs | 3 +- .../Effects/SmaaPostProcessingEffect.cs | 4 +- src/Ryujinx.Graphics.Vulkan/TextureBuffer.cs | 14 +- src/Ryujinx.Graphics.Vulkan/TextureView.cs | 20 +- 29 files changed, 435 insertions(+), 322 deletions(-) delete mode 100644 src/Ryujinx.Common/Memory/SpanOrArray.cs diff --git a/src/Ryujinx.Common/Memory/SpanOrArray.cs b/src/Ryujinx.Common/Memory/SpanOrArray.cs deleted file mode 100644 index 269ac02fd6..0000000000 --- a/src/Ryujinx.Common/Memory/SpanOrArray.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; - -namespace Ryujinx.Common.Memory -{ - /// - /// A struct that can represent both a Span and Array. - /// This is useful to keep the Array representation when possible to avoid copies. - /// - /// Element Type - public readonly ref struct SpanOrArray where T : unmanaged - { - public readonly T[] Array; - public readonly ReadOnlySpan Span; - - /// - /// Create a new SpanOrArray from an array. - /// - /// Array to store - public SpanOrArray(T[] array) - { - Array = array; - Span = ReadOnlySpan.Empty; - } - - /// - /// Create a new SpanOrArray from a readonly span. - /// - /// Span to store - public SpanOrArray(ReadOnlySpan span) - { - Array = null; - Span = span; - } - - /// - /// Return the contained array, or convert the span if necessary. - /// - /// An array containing the data - public T[] ToArray() - { - return Array ?? Span.ToArray(); - } - - /// - /// Return a ReadOnlySpan from either the array or ReadOnlySpan. - /// - /// A ReadOnlySpan containing the data - public ReadOnlySpan AsSpan() - { - return Array ?? Span; - } - - /// - /// Cast an array to a SpanOrArray. - /// - /// Source array - public static implicit operator SpanOrArray(T[] array) - { - return new SpanOrArray(array); - } - - /// - /// Cast a ReadOnlySpan to a SpanOrArray. - /// - /// Source ReadOnlySpan - public static implicit operator SpanOrArray(ReadOnlySpan span) - { - return new SpanOrArray(span); - } - - /// - /// Cast a Span to a SpanOrArray. - /// - /// Source Span - public static implicit operator SpanOrArray(Span span) - { - return new SpanOrArray(span); - } - - /// - /// Cast a SpanOrArray to a ReadOnlySpan - /// - /// Source SpanOrArray - public static implicit operator ReadOnlySpan(SpanOrArray spanOrArray) - { - return spanOrArray.AsSpan(); - } - } -} diff --git a/src/Ryujinx.Common/Utilities/EmbeddedResources.cs b/src/Ryujinx.Common/Utilities/EmbeddedResources.cs index a4facc2e37..e22571c966 100644 --- a/src/Ryujinx.Common/Utilities/EmbeddedResources.cs +++ b/src/Ryujinx.Common/Utilities/EmbeddedResources.cs @@ -1,5 +1,6 @@ using Ryujinx.Common.Utilities; using System; +using System.Buffers; using System.IO; using System.Linq; using System.Reflection; @@ -41,6 +42,22 @@ namespace Ryujinx.Common return StreamUtils.StreamToBytes(stream); } + public static IMemoryOwner ReadFileToRentedMemory(string filename) + { + var (assembly, path) = ResolveManifestPath(filename); + + return ReadFileToRentedMemory(assembly, path); + } + + public static IMemoryOwner ReadFileToRentedMemory(Assembly assembly, string filename) + { + using var stream = GetStream(assembly, filename); + + return stream is null + ? null + : StreamUtils.StreamToRentedMemory(stream); + } + public async static Task ReadAsync(Assembly assembly, string filename) { using var stream = GetStream(assembly, filename); diff --git a/src/Ryujinx.Common/Utilities/StreamUtils.cs b/src/Ryujinx.Common/Utilities/StreamUtils.cs index 7a20c98e95..74b6af5ecf 100644 --- a/src/Ryujinx.Common/Utilities/StreamUtils.cs +++ b/src/Ryujinx.Common/Utilities/StreamUtils.cs @@ -1,4 +1,6 @@ +using Microsoft.IO; using Ryujinx.Common.Memory; +using System.Buffers; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -9,12 +11,50 @@ namespace Ryujinx.Common.Utilities { public static byte[] StreamToBytes(Stream input) { - using MemoryStream stream = MemoryStreamManager.Shared.GetStream(); + using RecyclableMemoryStream output = StreamToRecyclableMemoryStream(input); + return output.ToArray(); + } - input.CopyTo(stream); + public static IMemoryOwner StreamToRentedMemory(Stream input) + { + if (input is MemoryStream inputMemoryStream) + { + return MemoryStreamToRentedMemory(inputMemoryStream); + } + else if (input.CanSeek) + { + long bytesExpected = input.Length; - return stream.ToArray(); + IMemoryOwner ownedMemory = ByteMemoryPool.Rent(bytesExpected); + + var destSpan = ownedMemory.Memory.Span; + + int totalBytesRead = 0; + + while (totalBytesRead < bytesExpected) + { + int bytesRead = input.Read(destSpan[totalBytesRead..]); + + if (bytesRead == 0) + { + ownedMemory.Dispose(); + + throw new IOException($"Tried reading {bytesExpected} but the stream closed after reading {totalBytesRead}."); + } + + totalBytesRead += bytesRead; + } + + return ownedMemory; + } + else + { + // If input is (non-seekable) then copy twice: first into a RecyclableMemoryStream, then to a rented IMemoryOwner. + using RecyclableMemoryStream output = StreamToRecyclableMemoryStream(input); + + return MemoryStreamToRentedMemory(output); + } } public static async Task StreamToBytesAsync(Stream input, CancellationToken cancellationToken = default) @@ -25,5 +65,26 @@ namespace Ryujinx.Common.Utilities return stream.ToArray(); } + + private static IMemoryOwner MemoryStreamToRentedMemory(MemoryStream input) + { + input.Position = 0; + + IMemoryOwner ownedMemory = ByteMemoryPool.Rent(input.Length); + + // Discard the return value because we assume reading a MemoryStream always succeeds completely. + _ = input.Read(ownedMemory.Memory.Span); + + return ownedMemory; + } + + private static RecyclableMemoryStream StreamToRecyclableMemoryStream(Stream input) + { + RecyclableMemoryStream stream = MemoryStreamManager.Shared.GetStream(); + + input.CopyTo(stream); + + return stream; + } } } diff --git a/src/Ryujinx.Graphics.Device/DeviceMemoryManager.cs b/src/Ryujinx.Graphics.Device/DeviceMemoryManager.cs index d64ed30954..fc075a2643 100644 --- a/src/Ryujinx.Graphics.Device/DeviceMemoryManager.cs +++ b/src/Ryujinx.Graphics.Device/DeviceMemoryManager.cs @@ -1,5 +1,7 @@ +using Ryujinx.Common.Memory; using Ryujinx.Memory; using System; +using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -143,11 +145,11 @@ namespace Ryujinx.Graphics.Device } else { - Memory memory = new byte[size]; + IMemoryOwner memoryOwner = ByteMemoryPool.Rent(size); - GetSpan(va, size).CopyTo(memory.Span); + GetSpan(va, size).CopyTo(memoryOwner.Memory.Span); - return new WritableRegion(this, va, memory, tracked: true); + return new WritableRegion(this, va, memoryOwner, tracked: true); } } diff --git a/src/Ryujinx.Graphics.GAL/ITexture.cs b/src/Ryujinx.Graphics.GAL/ITexture.cs index 5a4623a66d..2d9c6b7990 100644 --- a/src/Ryujinx.Graphics.GAL/ITexture.cs +++ b/src/Ryujinx.Graphics.GAL/ITexture.cs @@ -1,4 +1,4 @@ -using Ryujinx.Common.Memory; +using System.Buffers; namespace Ryujinx.Graphics.GAL { @@ -17,10 +17,34 @@ namespace Ryujinx.Graphics.GAL PinnedSpan GetData(); PinnedSpan GetData(int layer, int level); - void SetData(SpanOrArray data); - void SetData(SpanOrArray data, int layer, int level); - void SetData(SpanOrArray data, int layer, int level, Rectangle region); + /// + /// Sets the texture data. The data passed as a will be disposed when + /// the operation completes. + /// + /// Texture data bytes + void SetData(IMemoryOwner data); + + /// + /// Sets the texture data. The data passed as a will be disposed when + /// the operation completes. + /// + /// Texture data bytes + /// Target layer + /// Target level + void SetData(IMemoryOwner data, int layer, int level); + + /// + /// Sets the texture data. The data passed as a will be disposed when + /// the operation completes. + /// + /// Texture data bytes + /// Target layer + /// Target level + /// Target sub-region of the texture to update + void SetData(IMemoryOwner data, int layer, int level, Rectangle region); + void SetStorage(BufferRange buffer); + void Release(); } } diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataCommand.cs index 36feeeba75..3aba004dff 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataCommand.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataCommand.cs @@ -1,6 +1,6 @@ using Ryujinx.Graphics.GAL.Multithreading.Model; using Ryujinx.Graphics.GAL.Multithreading.Resources; -using System; +using System.Buffers; namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture { @@ -8,9 +8,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture { public readonly CommandType CommandType => CommandType.TextureSetData; private TableRef _texture; - private TableRef _data; + private TableRef> _data; - public void Set(TableRef texture, TableRef data) + public void Set(TableRef texture, TableRef> data) { _texture = texture; _data = data; @@ -19,7 +19,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture public static void Run(ref TextureSetDataCommand command, ThreadedRenderer threaded, IRenderer renderer) { ThreadedTexture texture = command._texture.Get(threaded); - texture.Base.SetData(new ReadOnlySpan(command._data.Get(threaded))); + texture.Base.SetData(command._data.Get(threaded)); } } } diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceCommand.cs index c50bfe089a..7ad709a757 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceCommand.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceCommand.cs @@ -1,6 +1,6 @@ using Ryujinx.Graphics.GAL.Multithreading.Model; using Ryujinx.Graphics.GAL.Multithreading.Resources; -using System; +using System.Buffers; namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture { @@ -8,11 +8,11 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture { public readonly CommandType CommandType => CommandType.TextureSetDataSlice; private TableRef _texture; - private TableRef _data; + private TableRef> _data; private int _layer; private int _level; - public void Set(TableRef texture, TableRef data, int layer, int level) + public void Set(TableRef texture, TableRef> data, int layer, int level) { _texture = texture; _data = data; @@ -23,7 +23,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture public static void Run(ref TextureSetDataSliceCommand command, ThreadedRenderer threaded, IRenderer renderer) { ThreadedTexture texture = command._texture.Get(threaded); - texture.Base.SetData(new ReadOnlySpan(command._data.Get(threaded)), command._layer, command._level); + texture.Base.SetData(command._data.Get(threaded), command._layer, command._level); } } } diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceRegionCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceRegionCommand.cs index 4c80d9bc3c..c211931bcc 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceRegionCommand.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceRegionCommand.cs @@ -1,6 +1,6 @@ using Ryujinx.Graphics.GAL.Multithreading.Model; using Ryujinx.Graphics.GAL.Multithreading.Resources; -using System; +using System.Buffers; namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture { @@ -8,12 +8,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture { public readonly CommandType CommandType => CommandType.TextureSetDataSliceRegion; private TableRef _texture; - private TableRef _data; + private TableRef> _data; private int _layer; private int _level; private Rectangle _region; - public void Set(TableRef texture, TableRef data, int layer, int level, Rectangle region) + public void Set(TableRef texture, TableRef> data, int layer, int level, Rectangle region) { _texture = texture; _data = data; @@ -25,7 +25,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture public static void Run(ref TextureSetDataSliceRegionCommand command, ThreadedRenderer threaded, IRenderer renderer) { ThreadedTexture texture = command._texture.Get(threaded); - texture.Base.SetData(new ReadOnlySpan(command._data.Get(threaded)), command._layer, command._level, command._region); + texture.Base.SetData(command._data.Get(threaded), command._layer, command._level, command._region); } } } diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs index 9ad9e64548..80003b8447 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs @@ -1,6 +1,6 @@ -using Ryujinx.Common.Memory; using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture; using Ryujinx.Graphics.GAL.Multithreading.Model; +using System.Buffers; namespace Ryujinx.Graphics.GAL.Multithreading.Resources { @@ -110,21 +110,24 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources _renderer.QueueCommand(); } - public void SetData(SpanOrArray data) + /// + public void SetData(IMemoryOwner data) { - _renderer.New().Set(Ref(this), Ref(data.ToArray())); + _renderer.New().Set(Ref(this), Ref(data)); _renderer.QueueCommand(); } - public void SetData(SpanOrArray data, int layer, int level) + /// + public void SetData(IMemoryOwner data, int layer, int level) { - _renderer.New().Set(Ref(this), Ref(data.ToArray()), layer, level); + _renderer.New().Set(Ref(this), Ref(data), layer, level); _renderer.QueueCommand(); } - public void SetData(SpanOrArray data, int layer, int level, Rectangle region) + /// + public void SetData(IMemoryOwner data, int layer, int level, Rectangle region) { - _renderer.New().Set(Ref(this), Ref(data.ToArray()), layer, level, region); + _renderer.New().Set(Ref(this), Ref(data), layer, level, region); _renderer.QueueCommand(); } diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs b/src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs index de395d574f..218db15cfd 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs @@ -4,6 +4,7 @@ using Ryujinx.Graphics.Gpu.Engine.Threed; using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Texture; using System; +using System.Buffers; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -308,7 +309,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma if (target != null) { - byte[] data; + IMemoryOwner data; if (srcLinear) { data = LayoutConverter.ConvertLinearStridedToLinear( diff --git a/src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs b/src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs index 37d74fdd34..93e43ce3c5 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs @@ -1,4 +1,5 @@ using Ryujinx.Common; +using Ryujinx.Common.Memory; using Ryujinx.Graphics.Device; using Ryujinx.Graphics.Texture; using System; @@ -198,7 +199,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory if (target != null) { target.SynchronizeMemory(); - target.SetData(data, 0, 0, new GAL.Rectangle(_dstX, _dstY, _lineLengthIn / target.Info.FormatInfo.BytesPerPixel, _lineCount)); + var dataCopy = ByteMemoryPool.RentCopy(data); + target.SetData(dataCopy, 0, 0, new GAL.Rectangle(_dstX, _dstY, _lineLengthIn / target.Info.FormatInfo.BytesPerPixel, _lineCount)); target.SignalModified(); return; diff --git a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs index 7a043b2b7c..e67caea81f 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -1,5 +1,4 @@ using Ryujinx.Common.Logging; -using Ryujinx.Common.Memory; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Texture; @@ -7,6 +6,7 @@ using Ryujinx.Graphics.Texture.Astc; using Ryujinx.Memory; using Ryujinx.Memory.Range; using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -661,7 +661,7 @@ namespace Ryujinx.Graphics.Gpu.Image } } - SpanOrArray result = ConvertToHostCompatibleFormat(data); + IMemoryOwner result = ConvertToHostCompatibleFormat(data); if (ScaleFactor != 1f && AllowScaledSetData()) { @@ -684,7 +684,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// Uploads new texture data to the host GPU. /// /// New data - public void SetData(SpanOrArray data) + public void SetData(IMemoryOwner data) { BlacklistScale(); @@ -703,7 +703,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// New data /// Target layer /// Target level - public void SetData(SpanOrArray data, int layer, int level) + public void SetData(IMemoryOwner data, int layer, int level) { BlacklistScale(); @@ -721,7 +721,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// Target layer /// Target level /// Target sub-region of the texture to update - public void SetData(ReadOnlySpan data, int layer, int level, Rectangle region) + public void SetData(IMemoryOwner data, int layer, int level, Rectangle region) { BlacklistScale(); @@ -739,7 +739,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// Mip level to convert /// True to convert a single slice /// Converted data - public SpanOrArray ConvertToHostCompatibleFormat(ReadOnlySpan data, int level = 0, bool single = false) + public IMemoryOwner ConvertToHostCompatibleFormat(ReadOnlySpan data, int level = 0, bool single = false) { int width = Info.Width; int height = Info.Height; @@ -754,11 +754,11 @@ namespace Ryujinx.Graphics.Gpu.Image int sliceDepth = single ? 1 : depth; - SpanOrArray result; + IMemoryOwner linear; if (Info.IsLinear) { - result = LayoutConverter.ConvertLinearStridedToLinear( + linear = LayoutConverter.ConvertLinearStridedToLinear( width, height, Info.FormatInfo.BlockWidth, @@ -770,7 +770,7 @@ namespace Ryujinx.Graphics.Gpu.Image } else { - result = LayoutConverter.ConvertBlockLinearToLinear( + linear = LayoutConverter.ConvertBlockLinearToLinear( width, height, depth, @@ -787,33 +787,41 @@ namespace Ryujinx.Graphics.Gpu.Image data); } + IMemoryOwner result = linear; + // Handle compressed cases not supported by the host: // - ASTC is usually not supported on desktop cards. // - BC4/BC5 is not supported on 3D textures. if (!_context.Capabilities.SupportsAstcCompression && Format.IsAstc()) { - if (!AstcDecoder.TryDecodeToRgba8P( - result.ToArray(), - Info.FormatInfo.BlockWidth, - Info.FormatInfo.BlockHeight, - width, - height, - sliceDepth, - levels, - layers, - out byte[] decoded)) + using (result) { - string texInfo = $"{Info.Target} {Info.FormatInfo.Format} {Info.Width}x{Info.Height}x{Info.DepthOrLayers} levels {Info.Levels}"; + if (!AstcDecoder.TryDecodeToRgba8P( + result.Memory, + Info.FormatInfo.BlockWidth, + Info.FormatInfo.BlockHeight, + width, + height, + sliceDepth, + levels, + layers, + out IMemoryOwner decoded)) + { + string texInfo = $"{Info.Target} {Info.FormatInfo.Format} {Info.Width}x{Info.Height}x{Info.DepthOrLayers} levels {Info.Levels}"; - Logger.Debug?.Print(LogClass.Gpu, $"Invalid ASTC texture at 0x{Info.GpuAddress:X} ({texInfo})."); + Logger.Debug?.Print(LogClass.Gpu, $"Invalid ASTC texture at 0x{Info.GpuAddress:X} ({texInfo})."); + } + + if (GraphicsConfig.EnableTextureRecompression) + { + using (decoded) + { + return BCnEncoder.EncodeBC7(decoded.Memory, width, height, sliceDepth, levels, layers); + } + } + + return decoded; } - - if (GraphicsConfig.EnableTextureRecompression) - { - decoded = BCnEncoder.EncodeBC7(decoded, width, height, sliceDepth, levels, layers); - } - - result = decoded; } else if (!_context.Capabilities.SupportsEtc2Compression && Format.IsEtc2()) { @@ -821,16 +829,22 @@ namespace Ryujinx.Graphics.Gpu.Image { case Format.Etc2RgbaSrgb: case Format.Etc2RgbaUnorm: - result = ETC2Decoder.DecodeRgba(result, width, height, sliceDepth, levels, layers); - break; + using (result) + { + return ETC2Decoder.DecodeRgba(result.Memory.Span, width, height, sliceDepth, levels, layers); + } case Format.Etc2RgbPtaSrgb: case Format.Etc2RgbPtaUnorm: - result = ETC2Decoder.DecodePta(result, width, height, sliceDepth, levels, layers); - break; + using (result) + { + return ETC2Decoder.DecodePta(result.Memory.Span, width, height, sliceDepth, levels, layers); + } case Format.Etc2RgbSrgb: case Format.Etc2RgbUnorm: - result = ETC2Decoder.DecodeRgb(result, width, height, sliceDepth, levels, layers); - break; + using (result) + { + return ETC2Decoder.DecodeRgb(result.Memory.Span, width, height, sliceDepth, levels, layers); + } } } else if (!TextureCompatibility.HostSupportsBcFormat(Format, Target, _context.Capabilities)) @@ -839,48 +853,75 @@ namespace Ryujinx.Graphics.Gpu.Image { case Format.Bc1RgbaSrgb: case Format.Bc1RgbaUnorm: - result = BCnDecoder.DecodeBC1(result, width, height, sliceDepth, levels, layers); - break; + using (result) + { + return BCnDecoder.DecodeBC1(result.Memory.Span, width, height, sliceDepth, levels, layers); + } case Format.Bc2Srgb: case Format.Bc2Unorm: - result = BCnDecoder.DecodeBC2(result, width, height, sliceDepth, levels, layers); - break; + using (result) + { + return BCnDecoder.DecodeBC2(result.Memory.Span, width, height, sliceDepth, levels, layers); + } case Format.Bc3Srgb: case Format.Bc3Unorm: - result = BCnDecoder.DecodeBC3(result, width, height, sliceDepth, levels, layers); - break; + using (result) + { + return BCnDecoder.DecodeBC3(result.Memory.Span, width, height, sliceDepth, levels, layers); + } case Format.Bc4Snorm: case Format.Bc4Unorm: - result = BCnDecoder.DecodeBC4(result, width, height, sliceDepth, levels, layers, Format == Format.Bc4Snorm); - break; + using (result) + { + return BCnDecoder.DecodeBC4(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc4Snorm); + } case Format.Bc5Snorm: case Format.Bc5Unorm: - result = BCnDecoder.DecodeBC5(result, width, height, sliceDepth, levels, layers, Format == Format.Bc5Snorm); - break; + using (result) + { + return BCnDecoder.DecodeBC5(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc5Snorm); + } case Format.Bc6HSfloat: case Format.Bc6HUfloat: - result = BCnDecoder.DecodeBC6(result, width, height, sliceDepth, levels, layers, Format == Format.Bc6HSfloat); - break; + using (result) + { + return BCnDecoder.DecodeBC6(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc6HSfloat); + } case Format.Bc7Srgb: case Format.Bc7Unorm: - result = BCnDecoder.DecodeBC7(result, width, height, sliceDepth, levels, layers); - break; + using (result) + { + return BCnDecoder.DecodeBC7(result.Memory.Span, width, height, sliceDepth, levels, layers); + } } } else if (!_context.Capabilities.SupportsR4G4Format && Format == Format.R4G4Unorm) { - result = PixelConverter.ConvertR4G4ToR4G4B4A4(result, width); - - if (!_context.Capabilities.SupportsR4G4B4A4Format) + using (result) { - result = PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result, width); + var converted = PixelConverter.ConvertR4G4ToR4G4B4A4(result.Memory.Span, width); + + if (_context.Capabilities.SupportsR4G4B4A4Format) + { + return converted; + } + else + { + using (converted) + { + return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(converted.Memory.Span, width); + } + } } } else if (Format == Format.R4G4B4A4Unorm) { if (!_context.Capabilities.SupportsR4G4B4A4Format) { - result = PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result, width); + using (result) + { + return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result.Memory.Span, width); + } } } else if (!_context.Capabilities.Supports5BitComponentFormat && Format.Is16BitPacked()) @@ -889,19 +930,27 @@ namespace Ryujinx.Graphics.Gpu.Image { case Format.B5G6R5Unorm: case Format.R5G6B5Unorm: - result = PixelConverter.ConvertR5G6B5ToR8G8B8A8(result, width); - break; + using (result) + { + return PixelConverter.ConvertR5G6B5ToR8G8B8A8(result.Memory.Span, width); + } case Format.B5G5R5A1Unorm: case Format.R5G5B5X1Unorm: case Format.R5G5B5A1Unorm: - result = PixelConverter.ConvertR5G5B5ToR8G8B8A8(result, width, Format == Format.R5G5B5X1Unorm); - break; + using (result) + { + return PixelConverter.ConvertR5G5B5ToR8G8B8A8(result.Memory.Span, width, Format == Format.R5G5B5X1Unorm); + } case Format.A1B5G5R5Unorm: - result = PixelConverter.ConvertA1B5G5R5ToR8G8B8A8(result, width); - break; + using (result) + { + return PixelConverter.ConvertA1B5G5R5ToR8G8B8A8(result.Memory.Span, width); + } case Format.R4G4B4A4Unorm: - result = PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result, width); - break; + using (result) + { + return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result.Memory.Span, width); + } } } diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs index 9785839426..de9c47c976 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs @@ -1,4 +1,3 @@ -using Ryujinx.Common.Memory; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Texture; @@ -6,6 +5,7 @@ using Ryujinx.Memory; using Ryujinx.Memory.Range; using Ryujinx.Memory.Tracking; using System; +using System.Buffers; using System.Collections.Generic; using System.Runtime.CompilerServices; @@ -445,7 +445,7 @@ namespace Ryujinx.Graphics.Gpu.Image ReadOnlySpan data = dataSpan[(offset - spanBase)..]; - SpanOrArray result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true); + IMemoryOwner result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true); Storage.SetData(result, info.BaseLayer + layer, info.BaseLevel + level); } diff --git a/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs b/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs index 30f87813f3..0b6c78fac3 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs @@ -1,6 +1,8 @@ +using Ryujinx.Common.Memory; using Ryujinx.Memory; using Ryujinx.Memory.Range; using System; +using System.Buffers; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -240,11 +242,11 @@ namespace Ryujinx.Graphics.Gpu.Memory } else { - Memory memory = new byte[size]; + IMemoryOwner memoryOwner = ByteMemoryPool.Rent(size); - GetSpan(va, size).CopyTo(memory.Span); + GetSpan(va, size).CopyTo(memoryOwner.Memory.Span); - return new WritableRegion(this, va, memory, tracked); + return new WritableRegion(this, va, memoryOwner, tracked); } } diff --git a/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs b/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs index ce970fab7c..4d09c3aabd 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs @@ -1,3 +1,4 @@ +using Ryujinx.Common.Memory; using Ryujinx.Cpu; using Ryujinx.Graphics.Device; using Ryujinx.Graphics.Gpu.Image; @@ -6,6 +7,7 @@ using Ryujinx.Memory; using Ryujinx.Memory.Range; using Ryujinx.Memory.Tracking; using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; @@ -190,7 +192,9 @@ namespace Ryujinx.Graphics.Gpu.Memory } else { - Memory memory = new byte[range.GetSize()]; + IMemoryOwner memoryOwner = ByteMemoryPool.Rent(range.GetSize()); + + Memory memory = memoryOwner.Memory; int offset = 0; for (int i = 0; i < range.Count; i++) @@ -204,7 +208,7 @@ namespace Ryujinx.Graphics.Gpu.Memory offset += size; } - return new WritableRegion(new MultiRangeWritableBlock(range, this), 0, memory, tracked); + return new WritableRegion(new MultiRangeWritableBlock(range, this), 0, memoryOwner, tracked); } } diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/SmaaPostProcessingEffect.cs b/src/Ryujinx.Graphics.OpenGL/Effects/SmaaPostProcessingEffect.cs index 46dda13f22..a6c5e4acab 100644 --- a/src/Ryujinx.Graphics.OpenGL/Effects/SmaaPostProcessingEffect.cs +++ b/src/Ryujinx.Graphics.OpenGL/Effects/SmaaPostProcessingEffect.cs @@ -33,7 +33,8 @@ namespace Ryujinx.Graphics.OpenGL.Effects.Smaa public int Quality { - get => _quality; set + get => _quality; + set { _quality = Math.Clamp(value, 0, _qualities.Length - 1); } @@ -150,8 +151,8 @@ namespace Ryujinx.Graphics.OpenGL.Effects.Smaa _areaTexture = new TextureStorage(_renderer, areaInfo); _searchTexture = new TextureStorage(_renderer, searchInfo); - var areaTexture = EmbeddedResources.Read("Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaAreaTexture.bin"); - var searchTexture = EmbeddedResources.Read("Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaSearchTexture.bin"); + var areaTexture = EmbeddedResources.ReadFileToRentedMemory("Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaAreaTexture.bin"); + var searchTexture = EmbeddedResources.ReadFileToRentedMemory("Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaSearchTexture.bin"); var areaView = _areaTexture.CreateDefaultView(); var searchView = _searchTexture.CreateDefaultView(); diff --git a/src/Ryujinx.Graphics.OpenGL/Image/FormatConverter.cs b/src/Ryujinx.Graphics.OpenGL/Image/FormatConverter.cs index c4bbf74566..434f25900c 100644 --- a/src/Ryujinx.Graphics.OpenGL/Image/FormatConverter.cs +++ b/src/Ryujinx.Graphics.OpenGL/Image/FormatConverter.cs @@ -1,4 +1,6 @@ +using Ryujinx.Common.Memory; using System; +using System.Buffers; using System.Numerics; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; @@ -8,9 +10,11 @@ namespace Ryujinx.Graphics.OpenGL.Image { static class FormatConverter { - public unsafe static byte[] ConvertS8D24ToD24S8(ReadOnlySpan data) + public unsafe static IMemoryOwner ConvertS8D24ToD24S8(ReadOnlySpan data) { - byte[] output = new byte[data.Length]; + IMemoryOwner outputMemory = ByteMemoryPool.Rent(data.Length); + + Span output = outputMemory.Memory.Span; int start = 0; @@ -74,7 +78,7 @@ namespace Ryujinx.Graphics.OpenGL.Image outSpan[i] = BitOperations.RotateLeft(dataSpan[i], 8); } - return output; + return outputMemory; } public unsafe static byte[] ConvertD24S8ToS8D24(ReadOnlySpan data) diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs index f140b276a5..a8196541a1 100644 --- a/src/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs +++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs @@ -1,7 +1,7 @@ using OpenTK.Graphics.OpenGL; -using Ryujinx.Common.Memory; using Ryujinx.Graphics.GAL; using System; +using System.Buffers; namespace Ryujinx.Graphics.OpenGL.Image { @@ -54,19 +54,24 @@ namespace Ryujinx.Graphics.OpenGL.Image throw new NotImplementedException(); } - public void SetData(SpanOrArray data) + /// + public void SetData(IMemoryOwner data) { - var dataSpan = data.AsSpan(); + var dataSpan = data.Memory.Span; Buffer.SetData(_buffer, _bufferOffset, dataSpan[..Math.Min(dataSpan.Length, _bufferSize)]); + + data.Dispose(); } - public void SetData(SpanOrArray data, int layer, int level) + /// + public void SetData(IMemoryOwner data, int layer, int level) { throw new NotSupportedException(); } - public void SetData(SpanOrArray data, int layer, int level, Rectangle region) + /// + public void SetData(IMemoryOwner data, int layer, int level, Rectangle region) { throw new NotSupportedException(); } diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs index 7f1b1c3824..8a18e6132a 100644 --- a/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs +++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs @@ -1,8 +1,8 @@ using OpenTK.Graphics.OpenGL; using Ryujinx.Common; -using Ryujinx.Common.Memory; using Ryujinx.Graphics.GAL; using System; +using System.Buffers; using System.Diagnostics; namespace Ryujinx.Graphics.OpenGL.Image @@ -448,70 +448,59 @@ namespace Ryujinx.Graphics.OpenGL.Image } } - public void SetData(SpanOrArray data) + public void SetData(IMemoryOwner data) { - var dataSpan = data.AsSpan(); - - if (Format == Format.S8UintD24Unorm) + using (data = EnsureDataFormat(data)) { - dataSpan = FormatConverter.ConvertS8D24ToD24S8(dataSpan); - } - - unsafe - { - fixed (byte* ptr = dataSpan) + unsafe { - ReadFrom((IntPtr)ptr, dataSpan.Length); + var dataSpan = data.Memory.Span; + fixed (byte* ptr = dataSpan) + { + ReadFrom((IntPtr)ptr, dataSpan.Length); + } } } } - public void SetData(SpanOrArray data, int layer, int level) + public void SetData(IMemoryOwner data, int layer, int level) { - var dataSpan = data.AsSpan(); - - if (Format == Format.S8UintD24Unorm) + using (data = EnsureDataFormat(data)) { - dataSpan = FormatConverter.ConvertS8D24ToD24S8(dataSpan); - } - - unsafe - { - fixed (byte* ptr = dataSpan) + unsafe { - int width = Math.Max(Info.Width >> level, 1); - int height = Math.Max(Info.Height >> level, 1); + fixed (byte* ptr = data.Memory.Span) + { + int width = Math.Max(Info.Width >> level, 1); + int height = Math.Max(Info.Height >> level, 1); - ReadFrom2D((IntPtr)ptr, layer, level, 0, 0, width, height); + ReadFrom2D((IntPtr)ptr, layer, level, 0, 0, width, height); + } } } } - public void SetData(SpanOrArray data, int layer, int level, Rectangle region) + public void SetData(IMemoryOwner data, int layer, int level, Rectangle region) { - var dataSpan = data.AsSpan(); - - if (Format == Format.S8UintD24Unorm) + using (data = EnsureDataFormat(data)) { - dataSpan = FormatConverter.ConvertS8D24ToD24S8(dataSpan); - } + int wInBlocks = BitUtils.DivRoundUp(region.Width, Info.BlockWidth); + int hInBlocks = BitUtils.DivRoundUp(region.Height, Info.BlockHeight); - int wInBlocks = BitUtils.DivRoundUp(region.Width, Info.BlockWidth); - int hInBlocks = BitUtils.DivRoundUp(region.Height, Info.BlockHeight); - - unsafe - { - fixed (byte* ptr = dataSpan) + unsafe { - ReadFrom2D( - (IntPtr)ptr, - layer, - level, - region.X, - region.Y, - region.Width, - region.Height, - BitUtils.AlignUp(wInBlocks * Info.BytesPerPixel, 4) * hInBlocks); + fixed (byte* ptr = data.Memory.Span) + { + ReadFrom2D( + (IntPtr)ptr, + layer, + level, + region.X, + region.Y, + region.Width, + region.Height, + BitUtils.AlignUp(wInBlocks * Info.BytesPerPixel, 4) * hInBlocks); + } } } } @@ -533,6 +522,19 @@ namespace Ryujinx.Graphics.OpenGL.Image ReadFrom2D(data, layer, level, x, y, width, height, mipSize); } + private IMemoryOwner EnsureDataFormat(IMemoryOwner data) + { + if (Format == Format.S8UintD24Unorm) + { + using (data) + { + return FormatConverter.ConvertS8D24ToD24S8(data.Memory.Span); + } + } + + return data; + } + private void ReadFrom2D(IntPtr data, int layer, int level, int x, int y, int width, int height, int mipSize) { TextureTarget target = Target.Convert(); diff --git a/src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs b/src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs index edf699dc32..3f65e1225b 100644 --- a/src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs +++ b/src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs @@ -1,5 +1,7 @@ +using Ryujinx.Common.Memory; using Ryujinx.Common.Utilities; using System; +using System.Buffers; using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; @@ -291,16 +293,14 @@ namespace Ryujinx.Graphics.Texture.Astc int depth, int levels, int layers, - out byte[] decoded) + out IMemoryOwner decoded) { - byte[] output = new byte[QueryDecompressedSize(width, height, depth, levels, layers)]; + decoded = ByteMemoryPool.Rent(QueryDecompressedSize(width, height, depth, levels, layers)); - AstcDecoder decoder = new(data, output, blockWidth, blockHeight, width, height, depth, levels, layers); + AstcDecoder decoder = new(data, decoded.Memory, blockWidth, blockHeight, width, height, depth, levels, layers); Enumerable.Range(0, decoder.TotalBlockCount).AsParallel().ForAll(x => decoder.ProcessBlock(x)); - decoded = output; - return decoder.Success; } diff --git a/src/Ryujinx.Graphics.Texture/BCnDecoder.cs b/src/Ryujinx.Graphics.Texture/BCnDecoder.cs index 2d68ca3468..eb85334a21 100644 --- a/src/Ryujinx.Graphics.Texture/BCnDecoder.cs +++ b/src/Ryujinx.Graphics.Texture/BCnDecoder.cs @@ -1,5 +1,7 @@ using Ryujinx.Common; +using Ryujinx.Common.Memory; using System; +using System.Buffers; using System.Buffers.Binary; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; @@ -12,7 +14,7 @@ namespace Ryujinx.Graphics.Texture private const int BlockWidth = 4; private const int BlockHeight = 4; - public static byte[] DecodeBC1(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) + public static IMemoryOwner DecodeBC1(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) { int size = 0; @@ -21,12 +23,12 @@ namespace Ryujinx.Graphics.Texture size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4; } - byte[] output = new byte[size]; + IMemoryOwner output = ByteMemoryPool.Rent(size); Span tile = stackalloc byte[BlockWidth * BlockHeight * 4]; Span tileAsUint = MemoryMarshal.Cast(tile); - Span outputAsUint = MemoryMarshal.Cast(output); + Span outputAsUint = MemoryMarshal.Cast(output.Memory.Span); Span> tileAsVector128 = MemoryMarshal.Cast>(tile); @@ -100,7 +102,7 @@ namespace Ryujinx.Graphics.Texture return output; } - public static byte[] DecodeBC2(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) + public static IMemoryOwner DecodeBC2(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) { int size = 0; @@ -109,12 +111,12 @@ namespace Ryujinx.Graphics.Texture size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4; } - byte[] output = new byte[size]; + IMemoryOwner output = ByteMemoryPool.Rent(size); Span tile = stackalloc byte[BlockWidth * BlockHeight * 4]; Span tileAsUint = MemoryMarshal.Cast(tile); - Span outputAsUint = MemoryMarshal.Cast(output); + Span outputAsUint = MemoryMarshal.Cast(output.Memory.Span); Span> tileAsVector128 = MemoryMarshal.Cast>(tile); @@ -195,7 +197,7 @@ namespace Ryujinx.Graphics.Texture return output; } - public static byte[] DecodeBC3(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) + public static IMemoryOwner DecodeBC3(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) { int size = 0; @@ -204,13 +206,13 @@ namespace Ryujinx.Graphics.Texture size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4; } - byte[] output = new byte[size]; + IMemoryOwner output = ByteMemoryPool.Rent(size); Span tile = stackalloc byte[BlockWidth * BlockHeight * 4]; Span rPal = stackalloc byte[8]; Span tileAsUint = MemoryMarshal.Cast(tile); - Span outputAsUint = MemoryMarshal.Cast(output); + Span outputAsUint = MemoryMarshal.Cast(output.Memory.Span); Span> tileAsVector128 = MemoryMarshal.Cast>(tile); @@ -292,7 +294,7 @@ namespace Ryujinx.Graphics.Texture return output; } - public static byte[] DecodeBC4(ReadOnlySpan data, int width, int height, int depth, int levels, int layers, bool signed) + public static IMemoryOwner DecodeBC4(ReadOnlySpan data, int width, int height, int depth, int levels, int layers, bool signed) { int size = 0; @@ -304,8 +306,8 @@ namespace Ryujinx.Graphics.Texture // Backends currently expect a stride alignment of 4 bytes, so output width must be aligned. int alignedWidth = BitUtils.AlignUp(width, 4); - byte[] output = new byte[size]; - Span outputSpan = new(output); + IMemoryOwner output = ByteMemoryPool.Rent(size); + Span outputSpan = output.Memory.Span; ReadOnlySpan data64 = MemoryMarshal.Cast(data); @@ -400,7 +402,7 @@ namespace Ryujinx.Graphics.Texture return output; } - public static byte[] DecodeBC5(ReadOnlySpan data, int width, int height, int depth, int levels, int layers, bool signed) + public static IMemoryOwner DecodeBC5(ReadOnlySpan data, int width, int height, int depth, int levels, int layers, bool signed) { int size = 0; @@ -412,7 +414,7 @@ namespace Ryujinx.Graphics.Texture // Backends currently expect a stride alignment of 4 bytes, so output width must be aligned. int alignedWidth = BitUtils.AlignUp(width, 2); - byte[] output = new byte[size]; + IMemoryOwner output = ByteMemoryPool.Rent(size); ReadOnlySpan data64 = MemoryMarshal.Cast(data); @@ -421,7 +423,7 @@ namespace Ryujinx.Graphics.Texture Span rPal = stackalloc byte[8]; Span gPal = stackalloc byte[8]; - Span outputAsUshort = MemoryMarshal.Cast(output); + Span outputAsUshort = MemoryMarshal.Cast(output.Memory.Span); Span rTileAsUint = MemoryMarshal.Cast(rTile); Span gTileAsUint = MemoryMarshal.Cast(gTile); @@ -525,7 +527,7 @@ namespace Ryujinx.Graphics.Texture return output; } - public static byte[] DecodeBC6(ReadOnlySpan data, int width, int height, int depth, int levels, int layers, bool signed) + public static IMemoryOwner DecodeBC6(ReadOnlySpan data, int width, int height, int depth, int levels, int layers, bool signed) { int size = 0; @@ -534,7 +536,8 @@ namespace Ryujinx.Graphics.Texture size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 8; } - byte[] output = new byte[size]; + IMemoryOwner output = ByteMemoryPool.Rent(size); + Span outputSpan = output.Memory.Span; int inputOffset = 0; int outputOffset = 0; @@ -548,7 +551,7 @@ namespace Ryujinx.Graphics.Texture { for (int z = 0; z < depth; z++) { - BC6Decoder.Decode(output.AsSpan()[outputOffset..], data[inputOffset..], width, height, signed); + BC6Decoder.Decode(outputSpan[outputOffset..], data[inputOffset..], width, height, signed); inputOffset += w * h * 16; outputOffset += width * height * 8; @@ -563,7 +566,7 @@ namespace Ryujinx.Graphics.Texture return output; } - public static byte[] DecodeBC7(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) + public static IMemoryOwner DecodeBC7(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) { int size = 0; @@ -572,7 +575,8 @@ namespace Ryujinx.Graphics.Texture size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4; } - byte[] output = new byte[size]; + IMemoryOwner output = ByteMemoryPool.Rent(size); + Span outputSpan = output.Memory.Span; int inputOffset = 0; int outputOffset = 0; @@ -586,7 +590,7 @@ namespace Ryujinx.Graphics.Texture { for (int z = 0; z < depth; z++) { - BC7Decoder.Decode(output.AsSpan()[outputOffset..], data[inputOffset..], width, height); + BC7Decoder.Decode(outputSpan[outputOffset..], data[inputOffset..], width, height); inputOffset += w * h * 16; outputOffset += width * height * 4; diff --git a/src/Ryujinx.Graphics.Texture/BCnEncoder.cs b/src/Ryujinx.Graphics.Texture/BCnEncoder.cs index 8103990ff3..253ba305cd 100644 --- a/src/Ryujinx.Graphics.Texture/BCnEncoder.cs +++ b/src/Ryujinx.Graphics.Texture/BCnEncoder.cs @@ -1,6 +1,8 @@ using Ryujinx.Common; +using Ryujinx.Common.Memory; using Ryujinx.Graphics.Texture.Encoders; using System; +using System.Buffers; namespace Ryujinx.Graphics.Texture { @@ -9,7 +11,7 @@ namespace Ryujinx.Graphics.Texture private const int BlockWidth = 4; private const int BlockHeight = 4; - public static byte[] EncodeBC7(byte[] data, int width, int height, int depth, int levels, int layers) + public static IMemoryOwner EncodeBC7(Memory data, int width, int height, int depth, int levels, int layers) { int size = 0; @@ -21,7 +23,8 @@ namespace Ryujinx.Graphics.Texture size += w * h * 16 * Math.Max(1, depth >> l) * layers; } - byte[] output = new byte[size]; + IMemoryOwner output = ByteMemoryPool.Rent(size); + Memory outputMemory = output.Memory; int imageBaseIOffs = 0; int imageBaseOOffs = 0; @@ -36,8 +39,8 @@ namespace Ryujinx.Graphics.Texture for (int z = 0; z < depth; z++) { BC7Encoder.Encode( - output.AsMemory()[imageBaseOOffs..], - data.AsMemory()[imageBaseIOffs..], + outputMemory[imageBaseOOffs..], + data[imageBaseIOffs..], width, height, EncodeMode.Fast | EncodeMode.Multithreaded); diff --git a/src/Ryujinx.Graphics.Texture/ETC2Decoder.cs b/src/Ryujinx.Graphics.Texture/ETC2Decoder.cs index 57f2e98d08..52801ff47d 100644 --- a/src/Ryujinx.Graphics.Texture/ETC2Decoder.cs +++ b/src/Ryujinx.Graphics.Texture/ETC2Decoder.cs @@ -1,5 +1,7 @@ using Ryujinx.Common; +using Ryujinx.Common.Memory; using System; +using System.Buffers; using System.Buffers.Binary; using System.Runtime.InteropServices; @@ -49,15 +51,15 @@ namespace Ryujinx.Graphics.Texture new int[] { -3, -5, -7, -9, 2, 4, 6, 8 }, }; - public static byte[] DecodeRgb(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) + public static IMemoryOwner DecodeRgb(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) { ReadOnlySpan dataUlong = MemoryMarshal.Cast(data); int inputOffset = 0; - byte[] output = new byte[CalculateOutputSize(width, height, depth, levels, layers)]; + IMemoryOwner output = ByteMemoryPool.Rent(CalculateOutputSize(width, height, depth, levels, layers)); - Span outputUint = MemoryMarshal.Cast(output); + Span outputUint = MemoryMarshal.Cast(output.Memory.Span); Span tile = stackalloc uint[BlockWidth * BlockHeight]; int imageBaseOOffs = 0; @@ -111,15 +113,15 @@ namespace Ryujinx.Graphics.Texture return output; } - public static byte[] DecodePta(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) + public static IMemoryOwner DecodePta(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) { ReadOnlySpan dataUlong = MemoryMarshal.Cast(data); int inputOffset = 0; - byte[] output = new byte[CalculateOutputSize(width, height, depth, levels, layers)]; + IMemoryOwner output = ByteMemoryPool.Rent(CalculateOutputSize(width, height, depth, levels, layers)); - Span outputUint = MemoryMarshal.Cast(output); + Span outputUint = MemoryMarshal.Cast(output.Memory.Span); Span tile = stackalloc uint[BlockWidth * BlockHeight]; int imageBaseOOffs = 0; @@ -168,15 +170,15 @@ namespace Ryujinx.Graphics.Texture return output; } - public static byte[] DecodeRgba(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) + public static IMemoryOwner DecodeRgba(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) { ReadOnlySpan dataUlong = MemoryMarshal.Cast(data); int inputOffset = 0; - byte[] output = new byte[CalculateOutputSize(width, height, depth, levels, layers)]; + IMemoryOwner output = ByteMemoryPool.Rent(CalculateOutputSize(width, height, depth, levels, layers)); - Span outputUint = MemoryMarshal.Cast(output); + Span outputUint = MemoryMarshal.Cast(output.Memory.Span); Span tile = stackalloc uint[BlockWidth * BlockHeight]; int imageBaseOOffs = 0; diff --git a/src/Ryujinx.Graphics.Texture/LayoutConverter.cs b/src/Ryujinx.Graphics.Texture/LayoutConverter.cs index d9a666c3f0..d6732674b5 100644 --- a/src/Ryujinx.Graphics.Texture/LayoutConverter.cs +++ b/src/Ryujinx.Graphics.Texture/LayoutConverter.cs @@ -1,5 +1,7 @@ using Ryujinx.Common; +using Ryujinx.Common.Memory; using System; +using System.Buffers; using System.Runtime.Intrinsics; using static Ryujinx.Graphics.Texture.BlockLinearConstants; @@ -93,7 +95,7 @@ namespace Ryujinx.Graphics.Texture }; } - public static byte[] ConvertBlockLinearToLinear( + public static IMemoryOwner ConvertBlockLinearToLinear( int width, int height, int depth, @@ -119,7 +121,8 @@ namespace Ryujinx.Graphics.Texture blockHeight, bytesPerPixel); - byte[] output = new byte[outSize]; + IMemoryOwner outputOwner = ByteMemoryPool.Rent(outSize); + Span output = outputOwner.Memory.Span; int outOffs = 0; @@ -243,10 +246,10 @@ namespace Ryujinx.Graphics.Texture _ => throw new NotSupportedException($"Unable to convert ${bytesPerPixel} bpp pixel format."), }; } - return output; + return outputOwner; } - public static byte[] ConvertLinearStridedToLinear( + public static IMemoryOwner ConvertLinearStridedToLinear( int width, int height, int blockWidth, @@ -262,8 +265,8 @@ namespace Ryujinx.Graphics.Texture int outStride = BitUtils.AlignUp(w * bytesPerPixel, HostStrideAlignment); lineSize = Math.Min(lineSize, outStride); - byte[] output = new byte[h * outStride]; - Span outSpan = output; + IMemoryOwner output = ByteMemoryPool.Rent(h * outStride); + Span outSpan = output.Memory.Span; int outOffs = 0; int inOffs = 0; diff --git a/src/Ryujinx.Graphics.Texture/PixelConverter.cs b/src/Ryujinx.Graphics.Texture/PixelConverter.cs index 7955aed3f1..4475cc98aa 100644 --- a/src/Ryujinx.Graphics.Texture/PixelConverter.cs +++ b/src/Ryujinx.Graphics.Texture/PixelConverter.cs @@ -1,5 +1,7 @@ using Ryujinx.Common; +using Ryujinx.Common.Memory; using System; +using System.Buffers; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; @@ -19,13 +21,13 @@ namespace Ryujinx.Graphics.Texture return (remainder, outRemainder, length / stride); } - public unsafe static byte[] ConvertR4G4ToR4G4B4A4(ReadOnlySpan data, int width) + public unsafe static IMemoryOwner ConvertR4G4ToR4G4B4A4(ReadOnlySpan data, int width) { - byte[] output = new byte[data.Length * 2]; + IMemoryOwner output = ByteMemoryPool.Rent(data.Length * 2); (int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 1, 2); - Span outputSpan = MemoryMarshal.Cast(output); + Span outputSpan = MemoryMarshal.Cast(output.Memory.Span); if (remainder == 0) { @@ -36,7 +38,7 @@ namespace Ryujinx.Graphics.Texture int sizeTrunc = data.Length & ~7; start = sizeTrunc; - fixed (byte* inputPtr = data, outputPtr = output) + fixed (byte* inputPtr = data, outputPtr = output.Memory.Span) { for (ulong offset = 0; offset < (ulong)sizeTrunc; offset += 8) { @@ -47,7 +49,7 @@ namespace Ryujinx.Graphics.Texture for (int i = start; i < data.Length; i++) { - outputSpan[i] = (ushort)data[i]; + outputSpan[i] = data[i]; } } else @@ -70,16 +72,16 @@ namespace Ryujinx.Graphics.Texture return output; } - public unsafe static byte[] ConvertR5G6B5ToR8G8B8A8(ReadOnlySpan data, int width) + public static IMemoryOwner ConvertR5G6B5ToR8G8B8A8(ReadOnlySpan data, int width) { - byte[] output = new byte[data.Length * 2]; + IMemoryOwner output = ByteMemoryPool.Rent(data.Length * 2); int offset = 0; int outOffset = 0; (int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4); ReadOnlySpan inputSpan = MemoryMarshal.Cast(data); - Span outputSpan = MemoryMarshal.Cast(output); + Span outputSpan = MemoryMarshal.Cast(output.Memory.Span); for (int y = 0; y < height; y++) { @@ -107,16 +109,16 @@ namespace Ryujinx.Graphics.Texture return output; } - public unsafe static byte[] ConvertR5G5B5ToR8G8B8A8(ReadOnlySpan data, int width, bool forceAlpha) + public static IMemoryOwner ConvertR5G5B5ToR8G8B8A8(ReadOnlySpan data, int width, bool forceAlpha) { - byte[] output = new byte[data.Length * 2]; + IMemoryOwner output = ByteMemoryPool.Rent(data.Length * 2); int offset = 0; int outOffset = 0; (int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4); ReadOnlySpan inputSpan = MemoryMarshal.Cast(data); - Span outputSpan = MemoryMarshal.Cast(output); + Span outputSpan = MemoryMarshal.Cast(output.Memory.Span); for (int y = 0; y < height; y++) { @@ -144,16 +146,16 @@ namespace Ryujinx.Graphics.Texture return output; } - public unsafe static byte[] ConvertA1B5G5R5ToR8G8B8A8(ReadOnlySpan data, int width) + public static IMemoryOwner ConvertA1B5G5R5ToR8G8B8A8(ReadOnlySpan data, int width) { - byte[] output = new byte[data.Length * 2]; + IMemoryOwner output = ByteMemoryPool.Rent(data.Length * 2); int offset = 0; int outOffset = 0; (int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4); ReadOnlySpan inputSpan = MemoryMarshal.Cast(data); - Span outputSpan = MemoryMarshal.Cast(output); + Span outputSpan = MemoryMarshal.Cast(output.Memory.Span); for (int y = 0; y < height; y++) { @@ -181,16 +183,16 @@ namespace Ryujinx.Graphics.Texture return output; } - public unsafe static byte[] ConvertR4G4B4A4ToR8G8B8A8(ReadOnlySpan data, int width) + public static IMemoryOwner ConvertR4G4B4A4ToR8G8B8A8(ReadOnlySpan data, int width) { - byte[] output = new byte[data.Length * 2]; + IMemoryOwner output = ByteMemoryPool.Rent(data.Length * 2); int offset = 0; int outOffset = 0; (int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4); ReadOnlySpan inputSpan = MemoryMarshal.Cast(data); - Span outputSpan = MemoryMarshal.Cast(output); + Span outputSpan = MemoryMarshal.Cast(output.Memory.Span); for (int y = 0; y < height; y++) { diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs index a0299a3720..a0010e660e 100644 --- a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs +++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs @@ -3,6 +3,7 @@ using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Shader; using Silk.NET.Vulkan; using System; +using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using CompareOp = Ryujinx.Graphics.GAL.CompareOp; @@ -216,7 +217,7 @@ namespace Ryujinx.Graphics.Vulkan public void Initialize() { - Span dummyTextureData = stackalloc byte[4]; + IMemoryOwner dummyTextureData = ByteMemoryPool.RentCleared(4); _dummyTexture.SetData(dummyTextureData); } diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs b/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs index 259be9d649..08e07f256b 100644 --- a/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs +++ b/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs @@ -174,8 +174,8 @@ namespace Ryujinx.Graphics.Vulkan.Effects SwizzleComponent.Blue, SwizzleComponent.Alpha); - var areaTexture = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaAreaTexture.bin"); - var searchTexture = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaSearchTexture.bin"); + var areaTexture = EmbeddedResources.ReadFileToRentedMemory("Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaAreaTexture.bin"); + var searchTexture = EmbeddedResources.ReadFileToRentedMemory("Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaSearchTexture.bin"); _areaTexture = _renderer.CreateTexture(areaInfo) as TextureView; _searchTexture = _renderer.CreateTexture(searchInfo) as TextureView; diff --git a/src/Ryujinx.Graphics.Vulkan/TextureBuffer.cs b/src/Ryujinx.Graphics.Vulkan/TextureBuffer.cs index 81e4788142..e0694b1979 100644 --- a/src/Ryujinx.Graphics.Vulkan/TextureBuffer.cs +++ b/src/Ryujinx.Graphics.Vulkan/TextureBuffer.cs @@ -1,7 +1,7 @@ -using Ryujinx.Common.Memory; using Ryujinx.Graphics.GAL; using Silk.NET.Vulkan; using System; +using System.Buffers; using System.Collections.Generic; using Format = Ryujinx.Graphics.GAL.Format; using VkFormat = Silk.NET.Vulkan.Format; @@ -94,17 +94,21 @@ namespace Ryujinx.Graphics.Vulkan _bufferView = null; } - public void SetData(SpanOrArray data) + /// + public void SetData(IMemoryOwner data) { - _gd.SetBufferData(_bufferHandle, _offset, data); + _gd.SetBufferData(_bufferHandle, _offset, data.Memory.Span); + data.Dispose(); } - public void SetData(SpanOrArray data, int layer, int level) + /// + public void SetData(IMemoryOwner data, int layer, int level) { throw new NotSupportedException(); } - public void SetData(SpanOrArray data, int layer, int level, Rectangle region) + /// + public void SetData(IMemoryOwner data, int layer, int level, Rectangle region) { throw new NotSupportedException(); } diff --git a/src/Ryujinx.Graphics.Vulkan/TextureView.cs b/src/Ryujinx.Graphics.Vulkan/TextureView.cs index d918f965fa..f2aaf4693f 100644 --- a/src/Ryujinx.Graphics.Vulkan/TextureView.cs +++ b/src/Ryujinx.Graphics.Vulkan/TextureView.cs @@ -1,7 +1,7 @@ -using Ryujinx.Common.Memory; using Ryujinx.Graphics.GAL; using Silk.NET.Vulkan; using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -702,19 +702,25 @@ namespace Ryujinx.Graphics.Vulkan return GetDataFromBuffer(result, size, result); } - public void SetData(SpanOrArray data) + /// + public void SetData(IMemoryOwner data) { - SetData(data, 0, 0, Info.GetLayers(), Info.Levels, singleSlice: false); + SetData(data.Memory.Span, 0, 0, Info.GetLayers(), Info.Levels, singleSlice: false); + data.Dispose(); } - public void SetData(SpanOrArray data, int layer, int level) + /// + public void SetData(IMemoryOwner data, int layer, int level) { - SetData(data, layer, level, 1, 1, singleSlice: true); + SetData(data.Memory.Span, layer, level, 1, 1, singleSlice: true); + data.Dispose(); } - public void SetData(SpanOrArray data, int layer, int level, Rectangle region) + /// + public void SetData(IMemoryOwner data, int layer, int level, Rectangle region) { - SetData(data, layer, level, 1, 1, singleSlice: true, region); + SetData(data.Memory.Span, layer, level, 1, 1, singleSlice: true, region); + data.Dispose(); } private void SetData(ReadOnlySpan data, int layer, int level, int layers, int levels, bool singleSlice, Rectangle? region = null) From 8884d1fd732c9ba788f0ab711e6a9f507d934ac8 Mon Sep 17 00:00:00 2001 From: Luke <44324377+Luke44565@users.noreply.github.com> Date: Mon, 15 Apr 2024 17:02:09 -0400 Subject: [PATCH 53/87] Fix crash when changing controller config (#6654) * fix needsMotionInputUpdate conditions * Fix formatting Co-authored-by: gdkchan --------- Co-authored-by: gdkchan --- src/Ryujinx.Input/HLE/NpadController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Ryujinx.Input/HLE/NpadController.cs b/src/Ryujinx.Input/HLE/NpadController.cs index f00db94e21..8411c10a79 100644 --- a/src/Ryujinx.Input/HLE/NpadController.cs +++ b/src/Ryujinx.Input/HLE/NpadController.cs @@ -245,9 +245,9 @@ namespace Ryujinx.Input.HLE { if (config is StandardControllerInputConfig controllerConfig) { - bool needsMotionInputUpdate = _config == null || (_config is StandardControllerInputConfig oldControllerConfig && - (oldControllerConfig.Motion.EnableMotion != controllerConfig.Motion.EnableMotion) && - (oldControllerConfig.Motion.MotionBackend != controllerConfig.Motion.MotionBackend)); + bool needsMotionInputUpdate = _config is not StandardControllerInputConfig oldControllerConfig || + ((oldControllerConfig.Motion.EnableMotion != controllerConfig.Motion.EnableMotion) && + (oldControllerConfig.Motion.MotionBackend != controllerConfig.Motion.MotionBackend)); if (needsMotionInputUpdate) { From 446f2854a5af18aef7151f95d64e0fe66a3498b8 Mon Sep 17 00:00:00 2001 From: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> Date: Wed, 17 Apr 2024 22:52:12 +0100 Subject: [PATCH 54/87] Ava UI: Input Menu Refactor (#5826) * Refactor * Apply suggestions from code review Co-authored-by: Ac_K * Update src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs Co-authored-by: Ac_K * Update src/Ryujinx.Input/ButtonValueType.cs Co-authored-by: Ac_K * Add empty line * Requested renames * Update src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs Co-authored-by: gdkchan * Make parent models private readonly * Fix ControllerInputView * Make line shorter * Mac keys in locale * Double line break * Fix build * Get rid of _isValid * Fix potential race condition * Rename HasAnyButtonPressed to IsAnyButtonPressed * Use switches * Simplify enumeration --------- Co-authored-by: Ac_K Co-authored-by: gdkchan Co-authored-by: TSR Berry <20988865+TSRBerry@users.noreply.github.com> --- .../Configuration/Hid/KeyboardHotkeys.cs | 2 - src/Ryujinx.Gtk3/UI/Helper/ButtonHelper.cs | 158 ++++ .../UI/Windows/ControllerWindow.cs | 6 +- .../Assigner/GamepadButtonAssigner.cs | 15 +- src/Ryujinx.Input/Assigner/IButtonAssigner.cs | 4 +- .../Assigner/KeyboardKeyAssigner.cs | 12 +- src/Ryujinx.Input/Button.cs | 33 + src/Ryujinx.Input/ButtonType.cs | 9 + src/Ryujinx.Input/HLE/NpadController.cs | 24 +- src/Ryujinx/Assets/Locales/en_US.json | 101 +++ src/Ryujinx/Assets/Styles/Styles.xaml | 5 +- src/Ryujinx/UI/Helpers/ButtonKeyAssigner.cs | 30 +- src/Ryujinx/UI/Helpers/KeyValueConverter.cs | 182 ++++- .../UI/Models/Input/GamepadInputConfig.cs | 580 +++++++++++++++ src/Ryujinx/UI/Models/Input/HotkeyConfig.cs | 141 ++++ .../UI/Models/Input/KeyboardInputConfig.cs | 422 +++++++++++ src/Ryujinx/UI/Models/InputConfiguration.cs | 456 ------------ .../Input/ControllerInputViewModel.cs | 84 +++ .../InputViewModel.cs} | 102 ++- .../Input/KeyboardInputViewModel.cs | 73 ++ .../{ => Input}/MotionInputViewModel.cs | 2 +- .../{ => Input}/RumbleInputViewModel.cs | 2 +- .../UI/ViewModels/SettingsViewModel.cs | 18 +- .../UI/Views/Input/ControllerInputView.axaml | 617 ++++------------ .../Views/Input/ControllerInputView.axaml.cs | 165 +++-- src/Ryujinx/UI/Views/Input/InputView.axaml | 225 ++++++ src/Ryujinx/UI/Views/Input/InputView.axaml.cs | 61 ++ .../UI/Views/Input/KeyboardInputView.axaml | 675 ++++++++++++++++++ .../UI/Views/Input/KeyboardInputView.axaml.cs | 208 ++++++ .../UI/Views/Input/MotionInputView.axaml | 2 +- .../UI/Views/Input/MotionInputView.axaml.cs | 8 +- .../UI/Views/Input/RumbleInputView.axaml | 2 +- .../UI/Views/Input/RumbleInputView.axaml.cs | 8 +- .../Views/Settings/SettingsHotkeysView.axaml | 120 ++-- .../Settings/SettingsHotkeysView.axaml.cs | 117 ++- .../UI/Views/Settings/SettingsInputView.axaml | 4 +- .../Views/Settings/SettingsInputView.axaml.cs | 2 +- .../UI/Windows/SettingsWindow.axaml.cs | 2 +- 38 files changed, 3402 insertions(+), 1275 deletions(-) create mode 100644 src/Ryujinx.Gtk3/UI/Helper/ButtonHelper.cs create mode 100644 src/Ryujinx.Input/Button.cs create mode 100644 src/Ryujinx.Input/ButtonType.cs create mode 100644 src/Ryujinx/UI/Models/Input/GamepadInputConfig.cs create mode 100644 src/Ryujinx/UI/Models/Input/HotkeyConfig.cs create mode 100644 src/Ryujinx/UI/Models/Input/KeyboardInputConfig.cs delete mode 100644 src/Ryujinx/UI/Models/InputConfiguration.cs create mode 100644 src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs rename src/Ryujinx/UI/ViewModels/{ControllerInputViewModel.cs => Input/InputViewModel.cs} (92%) create mode 100644 src/Ryujinx/UI/ViewModels/Input/KeyboardInputViewModel.cs rename src/Ryujinx/UI/ViewModels/{ => Input}/MotionInputViewModel.cs (97%) rename src/Ryujinx/UI/ViewModels/{ => Input}/RumbleInputViewModel.cs (92%) create mode 100644 src/Ryujinx/UI/Views/Input/InputView.axaml create mode 100644 src/Ryujinx/UI/Views/Input/InputView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml create mode 100644 src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml.cs diff --git a/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs b/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs index e9c163cf20..0cb49ca8ce 100644 --- a/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs +++ b/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs @@ -1,7 +1,5 @@ namespace Ryujinx.Common.Configuration.Hid { - // NOTE: Please don't change this to struct. - // This breaks Avalonia's TwoWay binding, which makes us unable to save new KeyboardHotkeys. public class KeyboardHotkeys { public Key ToggleVsync { get; set; } diff --git a/src/Ryujinx.Gtk3/UI/Helper/ButtonHelper.cs b/src/Ryujinx.Gtk3/UI/Helper/ButtonHelper.cs new file mode 100644 index 0000000000..5a8ca96a19 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Helper/ButtonHelper.cs @@ -0,0 +1,158 @@ +using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Input; +using System; +using System.Collections.Generic; +using Key = Ryujinx.Common.Configuration.Hid.Key; +using StickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; + +namespace Ryujinx.UI.Helper +{ + public static class ButtonHelper + { + private static readonly Dictionary _keysMap = new() + { + { Key.Unknown, "Unknown" }, + { Key.ShiftLeft, "ShiftLeft" }, + { Key.ShiftRight, "ShiftRight" }, + { Key.ControlLeft, "CtrlLeft" }, + { Key.ControlRight, "CtrlRight" }, + { Key.AltLeft, OperatingSystem.IsMacOS() ? "OptLeft" : "AltLeft" }, + { Key.AltRight, OperatingSystem.IsMacOS() ? "OptRight" : "AltRight" }, + { Key.WinLeft, OperatingSystem.IsMacOS() ? "CmdLeft" : "WinLeft" }, + { Key.WinRight, OperatingSystem.IsMacOS() ? "CmdRight" : "WinRight" }, + { Key.Up, "Up" }, + { Key.Down, "Down" }, + { Key.Left, "Left" }, + { Key.Right, "Right" }, + { Key.Enter, "Enter" }, + { Key.Escape, "Escape" }, + { Key.Space, "Space" }, + { Key.Tab, "Tab" }, + { Key.BackSpace, "Backspace" }, + { Key.Insert, "Insert" }, + { Key.Delete, "Delete" }, + { Key.PageUp, "PageUp" }, + { Key.PageDown, "PageDown" }, + { Key.Home, "Home" }, + { Key.End, "End" }, + { Key.CapsLock, "CapsLock" }, + { Key.ScrollLock, "ScrollLock" }, + { Key.PrintScreen, "PrintScreen" }, + { Key.Pause, "Pause" }, + { Key.NumLock, "NumLock" }, + { Key.Clear, "Clear" }, + { Key.Keypad0, "Keypad0" }, + { Key.Keypad1, "Keypad1" }, + { Key.Keypad2, "Keypad2" }, + { Key.Keypad3, "Keypad3" }, + { Key.Keypad4, "Keypad4" }, + { Key.Keypad5, "Keypad5" }, + { Key.Keypad6, "Keypad6" }, + { Key.Keypad7, "Keypad7" }, + { Key.Keypad8, "Keypad8" }, + { Key.Keypad9, "Keypad9" }, + { Key.KeypadDivide, "KeypadDivide" }, + { Key.KeypadMultiply, "KeypadMultiply" }, + { Key.KeypadSubtract, "KeypadSubtract" }, + { Key.KeypadAdd, "KeypadAdd" }, + { Key.KeypadDecimal, "KeypadDecimal" }, + { Key.KeypadEnter, "KeypadEnter" }, + { Key.Number0, "0" }, + { Key.Number1, "1" }, + { Key.Number2, "2" }, + { Key.Number3, "3" }, + { Key.Number4, "4" }, + { Key.Number5, "5" }, + { Key.Number6, "6" }, + { Key.Number7, "7" }, + { Key.Number8, "8" }, + { Key.Number9, "9" }, + { Key.Tilde, "~" }, + { Key.Grave, "`" }, + { Key.Minus, "-" }, + { Key.Plus, "+" }, + { Key.BracketLeft, "[" }, + { Key.BracketRight, "]" }, + { Key.Semicolon, ";" }, + { Key.Quote, "'" }, + { Key.Comma, "," }, + { Key.Period, "." }, + { Key.Slash, "/" }, + { Key.BackSlash, "\\" }, + { Key.Unbound, "Unbound" }, + }; + + private static readonly Dictionary _gamepadInputIdMap = new() + { + { GamepadInputId.LeftStick, "LeftStick" }, + { GamepadInputId.RightStick, "RightStick" }, + { GamepadInputId.LeftShoulder, "LeftShoulder" }, + { GamepadInputId.RightShoulder, "RightShoulder" }, + { GamepadInputId.LeftTrigger, "LeftTrigger" }, + { GamepadInputId.RightTrigger, "RightTrigger" }, + { GamepadInputId.DpadUp, "DpadUp" }, + { GamepadInputId.DpadDown, "DpadDown" }, + { GamepadInputId.DpadLeft, "DpadLeft" }, + { GamepadInputId.DpadRight, "DpadRight" }, + { GamepadInputId.Minus, "Minus" }, + { GamepadInputId.Plus, "Plus" }, + { GamepadInputId.Guide, "Guide" }, + { GamepadInputId.Misc1, "Misc1" }, + { GamepadInputId.Paddle1, "Paddle1" }, + { GamepadInputId.Paddle2, "Paddle2" }, + { GamepadInputId.Paddle3, "Paddle3" }, + { GamepadInputId.Paddle4, "Paddle4" }, + { GamepadInputId.Touchpad, "Touchpad" }, + { GamepadInputId.SingleLeftTrigger0, "SingleLeftTrigger0" }, + { GamepadInputId.SingleRightTrigger0, "SingleRightTrigger0" }, + { GamepadInputId.SingleLeftTrigger1, "SingleLeftTrigger1" }, + { GamepadInputId.SingleRightTrigger1, "SingleRightTrigger1" }, + { GamepadInputId.Unbound, "Unbound" }, + }; + + private static readonly Dictionary _stickInputIdMap = new() + { + { StickInputId.Left, "StickLeft" }, + { StickInputId.Right, "StickRight" }, + { StickInputId.Unbound, "Unbound" }, + }; + + public static string ToString(Button button) + { + string keyString = ""; + + switch (button.Type) + { + case ButtonType.Key: + var key = button.AsHidType(); + + if (!_keysMap.TryGetValue(button.AsHidType(), out keyString)) + { + keyString = key.ToString(); + } + + break; + case ButtonType.GamepadButtonInputId: + var gamepadButton = button.AsHidType(); + + if (!_gamepadInputIdMap.TryGetValue(button.AsHidType(), out keyString)) + { + keyString = gamepadButton.ToString(); + } + + break; + case ButtonType.StickId: + var stickInput = button.AsHidType(); + + if (!_stickInputIdMap.TryGetValue(button.AsHidType(), out keyString)) + { + keyString = stickInput.ToString(); + } + + break; + } + + return keyString; + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Windows/ControllerWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/ControllerWindow.cs index 6712657f8d..d0b8266f42 100644 --- a/src/Ryujinx.Gtk3/UI/Windows/ControllerWindow.cs +++ b/src/Ryujinx.Gtk3/UI/Windows/ControllerWindow.cs @@ -10,6 +10,7 @@ using Ryujinx.Input; using Ryujinx.Input.Assigner; using Ryujinx.Input.GTK3; using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Helper; using Ryujinx.UI.Widgets; using System; using System.Collections.Generic; @@ -17,6 +18,7 @@ using System.IO; using System.Reflection; using System.Text.Json; using System.Threading; +using Button = Ryujinx.Input.Button; using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId; using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; using GUI = Gtk.Builder.ObjectAttribute; @@ -887,13 +889,13 @@ namespace Ryujinx.UI.Windows Thread.Sleep(10); assigner.ReadInput(); - if (_mousePressed || keyboard.IsPressed(Ryujinx.Input.Key.Escape) || assigner.HasAnyButtonPressed() || assigner.ShouldCancel()) + if (_mousePressed || keyboard.IsPressed(Ryujinx.Input.Key.Escape) || assigner.IsAnyButtonPressed() || assigner.ShouldCancel()) { break; } } - string pressedButton = assigner.GetPressedButton(); + string pressedButton = ButtonHelper.ToString(assigner.GetPressedButton() ?? new Button(Input.Key.Unknown)); Application.Invoke(delegate { diff --git a/src/Ryujinx.Input/Assigner/GamepadButtonAssigner.cs b/src/Ryujinx.Input/Assigner/GamepadButtonAssigner.cs index 388ebcc07a..80fed2b82e 100644 --- a/src/Ryujinx.Input/Assigner/GamepadButtonAssigner.cs +++ b/src/Ryujinx.Input/Assigner/GamepadButtonAssigner.cs @@ -49,9 +49,9 @@ namespace Ryujinx.Input.Assigner CollectButtonStats(); } - public bool HasAnyButtonPressed() + public bool IsAnyButtonPressed() { - return _detector.HasAnyButtonPressed(); + return _detector.IsAnyButtonPressed(); } public bool ShouldCancel() @@ -59,16 +59,11 @@ namespace Ryujinx.Input.Assigner return _gamepad == null || !_gamepad.IsConnected; } - public string GetPressedButton() + public Button? GetPressedButton() { IEnumerable pressedButtons = _detector.GetPressedButtons(); - if (pressedButtons.Any()) - { - return !_forStick ? pressedButtons.First().ToString() : ((StickInputId)pressedButtons.First()).ToString(); - } - - return ""; + return !_forStick ? new(pressedButtons.FirstOrDefault()) : new((StickInputId)pressedButtons.FirstOrDefault()); } private void CollectButtonStats() @@ -123,7 +118,7 @@ namespace Ryujinx.Input.Assigner _stats = new Dictionary(); } - public bool HasAnyButtonPressed() + public bool IsAnyButtonPressed() { return _stats.Values.Any(CheckButtonPressed); } diff --git a/src/Ryujinx.Input/Assigner/IButtonAssigner.cs b/src/Ryujinx.Input/Assigner/IButtonAssigner.cs index 76a9fece49..688fbddb44 100644 --- a/src/Ryujinx.Input/Assigner/IButtonAssigner.cs +++ b/src/Ryujinx.Input/Assigner/IButtonAssigner.cs @@ -19,7 +19,7 @@ namespace Ryujinx.Input.Assigner /// Check if a button was pressed. /// /// True if a button was pressed - bool HasAnyButtonPressed(); + bool IsAnyButtonPressed(); /// /// Indicate if the user of this API should cancel operations. This is triggered for example when a gamepad get disconnected or when a user cancel assignation operations. @@ -31,6 +31,6 @@ namespace Ryujinx.Input.Assigner /// Get the pressed button that was read in by the button assigner. /// /// The pressed button that was read - string GetPressedButton(); + Button? GetPressedButton(); } } diff --git a/src/Ryujinx.Input/Assigner/KeyboardKeyAssigner.cs b/src/Ryujinx.Input/Assigner/KeyboardKeyAssigner.cs index e52ef4a2ce..3c011a63b8 100644 --- a/src/Ryujinx.Input/Assigner/KeyboardKeyAssigner.cs +++ b/src/Ryujinx.Input/Assigner/KeyboardKeyAssigner.cs @@ -21,9 +21,9 @@ namespace Ryujinx.Input.Assigner _keyboardState = _keyboard.GetKeyboardStateSnapshot(); } - public bool HasAnyButtonPressed() + public bool IsAnyButtonPressed() { - return GetPressedButton().Length != 0; + return GetPressedButton() is not null; } public bool ShouldCancel() @@ -31,20 +31,20 @@ namespace Ryujinx.Input.Assigner return _keyboardState.IsPressed(Key.Escape); } - public string GetPressedButton() + public Button? GetPressedButton() { - string keyPressed = ""; + Button? keyPressed = null; for (Key key = Key.Unknown; key < Key.Count; key++) { if (_keyboardState.IsPressed(key)) { - keyPressed = key.ToString(); + keyPressed = new(key); break; } } - return !ShouldCancel() ? keyPressed : ""; + return !ShouldCancel() ? keyPressed : null; } } } diff --git a/src/Ryujinx.Input/Button.cs b/src/Ryujinx.Input/Button.cs new file mode 100644 index 0000000000..4289901ce9 --- /dev/null +++ b/src/Ryujinx.Input/Button.cs @@ -0,0 +1,33 @@ +using System; + +namespace Ryujinx.Input +{ + public readonly struct Button + { + public readonly ButtonType Type; + private readonly uint _rawValue; + + public Button(Key key) + { + Type = ButtonType.Key; + _rawValue = (uint)key; + } + + public Button(GamepadButtonInputId gamepad) + { + Type = ButtonType.GamepadButtonInputId; + _rawValue = (uint)gamepad; + } + + public Button(StickInputId stick) + { + Type = ButtonType.StickId; + _rawValue = (uint)stick; + } + + public T AsHidType() where T : Enum + { + return (T)Enum.ToObject(typeof(T), _rawValue); + } + } +} diff --git a/src/Ryujinx.Input/ButtonType.cs b/src/Ryujinx.Input/ButtonType.cs new file mode 100644 index 0000000000..25ef5eea81 --- /dev/null +++ b/src/Ryujinx.Input/ButtonType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Input +{ + public enum ButtonType + { + Key, + GamepadButtonInputId, + StickId, + } +} diff --git a/src/Ryujinx.Input/HLE/NpadController.cs b/src/Ryujinx.Input/HLE/NpadController.cs index 8411c10a79..cde20f5d07 100644 --- a/src/Ryujinx.Input/HLE/NpadController.cs +++ b/src/Ryujinx.Input/HLE/NpadController.cs @@ -203,8 +203,6 @@ namespace Ryujinx.Input.HLE new(Key.NumLock, 10), }; - private bool _isValid; - private MotionInput _leftMotionInput; private MotionInput _rightMotionInput; @@ -222,7 +220,6 @@ namespace Ryujinx.Input.HLE { State = default; Id = null; - _isValid = false; _cemuHookClient = cemuHookClient; } @@ -234,11 +231,10 @@ namespace Ryujinx.Input.HLE Id = config.Id; _gamepad = GamepadDriver.GetGamepad(Id); - _isValid = _gamepad != null; UpdateUserConfiguration(config); - return _isValid; + return _gamepad != null; } public void UpdateUserConfiguration(InputConfig config) @@ -262,10 +258,7 @@ namespace Ryujinx.Input.HLE _config = config; - if (_isValid) - { - _gamepad.SetConfiguration(config); - } + _gamepad?.SetConfiguration(config); } private void UpdateMotionInput(MotionConfigController motionConfig) @@ -282,18 +275,21 @@ namespace Ryujinx.Input.HLE public void Update() { - if (_isValid && GamepadDriver != null) + // _gamepad may be altered by other threads + var gamepad = _gamepad; + + if (gamepad != null && GamepadDriver != null) { - State = _gamepad.GetMappedStateSnapshot(); + State = gamepad.GetMappedStateSnapshot(); if (_config is StandardControllerInputConfig controllerConfig && controllerConfig.Motion.EnableMotion) { if (controllerConfig.Motion.MotionBackend == MotionInputBackendType.GamepadDriver) { - if (_gamepad.Features.HasFlag(GamepadFeaturesFlag.Motion)) + if (gamepad.Features.HasFlag(GamepadFeaturesFlag.Motion)) { - Vector3 accelerometer = _gamepad.GetMotionData(MotionInputId.Accelerometer); - Vector3 gyroscope = _gamepad.GetMotionData(MotionInputId.Gyroscope); + Vector3 accelerometer = gamepad.GetMotionData(MotionInputId.Accelerometer); + Vector3 gyroscope = gamepad.GetMotionData(MotionInputId.Gyroscope); accelerometer = new Vector3(accelerometer.X, -accelerometer.Z, accelerometer.Y); gyroscope = new Vector3(gyroscope.X, -gyroscope.Z, gyroscope.Y); diff --git a/src/Ryujinx/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json index ef40fd5b2e..77ad7d1f8c 100644 --- a/src/Ryujinx/Assets/Locales/en_US.json +++ b/src/Ryujinx/Assets/Locales/en_US.json @@ -266,6 +266,107 @@ "ControllerSettingsMotionGyroDeadzone": "Gyro Deadzone:", "ControllerSettingsSave": "Save", "ControllerSettingsClose": "Close", + "KeyUnknown": "Unknown", + "KeyShiftLeft": "Shift Left", + "KeyShiftRight": "Shift Right", + "KeyControlLeft": "Ctrl Left", + "KeyMacControlLeft": "⌃ Left", + "KeyControlRight": "Ctrl Right", + "KeyMacControlRight": "⌃ Right", + "KeyAltLeft": "Alt Left", + "KeyMacAltLeft": "⌥ Left", + "KeyAltRight": "Alt Right", + "KeyMacAltRight": "⌥ Right", + "KeyWinLeft": "⊞ Left", + "KeyMacWinLeft": "⌘ Left", + "KeyWinRight": "⊞ Right", + "KeyMacWinRight": "⌘ Right", + "KeyMenu": "Menu", + "KeyUp": "Up", + "KeyDown": "Down", + "KeyLeft": "Left", + "KeyRight": "Right", + "KeyEnter": "Enter", + "KeyEscape": "Escape", + "KeySpace": "Space", + "KeyTab": "Tab", + "KeyBackSpace": "Backspace", + "KeyInsert": "Insert", + "KeyDelete": "Delete", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "Print Screen", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "Clear", + "KeyKeypad0": "Keypad 0", + "KeyKeypad1": "Keypad 1", + "KeyKeypad2": "Keypad 2", + "KeyKeypad3": "Keypad 3", + "KeyKeypad4": "Keypad 4", + "KeyKeypad5": "Keypad 5", + "KeyKeypad6": "Keypad 6", + "KeyKeypad7": "Keypad 7", + "KeyKeypad8": "Keypad 8", + "KeyKeypad9": "Keypad 9", + "KeyKeypadDivide": "Keypad Divide", + "KeyKeypadMultiply": "Keypad Multiply", + "KeyKeypadSubtract": "Keypad Subtract", + "KeyKeypadAdd": "Keypad Add", + "KeyKeypadDecimal": "Keypad Decimal", + "KeyKeypadEnter": "Keypad Enter", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "Unbound", + "GamepadLeftStick": "L Stick Button", + "GamepadRightStick": "R Stick Button", + "GamepadLeftShoulder": "Left Shoulder", + "GamepadRightShoulder": "Right Shoulder", + "GamepadLeftTrigger": "Left Trigger", + "GamepadRightTrigger": "Right Trigger", + "GamepadDpadUp": "Up", + "GamepadDpadDown": "Down", + "GamepadDpadLeft": "Left", + "GamepadDpadRight": "Right", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "Guide", + "GamepadMisc1": "Misc", + "GamepadPaddle1": "Paddle 1", + "GamepadPaddle2": "Paddle 2", + "GamepadPaddle3": "Paddle 3", + "GamepadPaddle4": "Paddle 4", + "GamepadTouchpad": "Touchpad", + "GamepadSingleLeftTrigger0": "Left Trigger 0", + "GamepadSingleRightTrigger0": "Right Trigger 0", + "GamepadSingleLeftTrigger1": "Left Trigger 1", + "GamepadSingleRightTrigger1": "Right Trigger 1", + "StickLeft": "Left Stick", + "StickRight": "Right Stick", "UserProfilesSelectedUserProfile": "Selected User Profile:", "UserProfilesSaveProfileName": "Save Profile Name", "UserProfilesChangeProfileImage": "Change Profile Image", diff --git a/src/Ryujinx/Assets/Styles/Styles.xaml b/src/Ryujinx/Assets/Styles/Styles.xaml index f7f64be22f..b3a6f59c8d 100644 --- a/src/Ryujinx/Assets/Styles/Styles.xaml +++ b/src/Ryujinx/Assets/Styles/Styles.xaml @@ -15,8 +15,7 @@ - + @@ -393,4 +392,4 @@ 600 756 - \ No newline at end of file + diff --git a/src/Ryujinx/UI/Helpers/ButtonKeyAssigner.cs b/src/Ryujinx/UI/Helpers/ButtonKeyAssigner.cs index 7e8ba73421..4f2b8daaea 100644 --- a/src/Ryujinx/UI/Helpers/ButtonKeyAssigner.cs +++ b/src/Ryujinx/UI/Helpers/ButtonKeyAssigner.cs @@ -1,11 +1,8 @@ -using Avalonia.Controls; using Avalonia.Controls.Primitives; -using Avalonia.LogicalTree; using Avalonia.Threading; using Ryujinx.Input; using Ryujinx.Input.Assigner; using System; -using System.Linq; using System.Threading.Tasks; namespace Ryujinx.Ava.UI.Helpers @@ -15,12 +12,12 @@ namespace Ryujinx.Ava.UI.Helpers internal class ButtonAssignedEventArgs : EventArgs { public ToggleButton Button { get; } - public bool IsAssigned { get; } + public Button? ButtonValue { get; } - public ButtonAssignedEventArgs(ToggleButton button, bool isAssigned) + public ButtonAssignedEventArgs(ToggleButton button, Button? buttonValue) { Button = button; - IsAssigned = isAssigned; + ButtonValue = buttonValue; } } @@ -69,7 +66,7 @@ namespace Ryujinx.Ava.UI.Helpers assigner.ReadInput(); - if (assigner.HasAnyButtonPressed() || assigner.ShouldCancel() || (keyboard != null && keyboard.IsPressed(Key.Escape))) + if (assigner.IsAnyButtonPressed() || assigner.ShouldCancel() || (keyboard != null && keyboard.IsPressed(Key.Escape))) { break; } @@ -78,15 +75,11 @@ namespace Ryujinx.Ava.UI.Helpers await Dispatcher.UIThread.InvokeAsync(() => { - string pressedButton = assigner.GetPressedButton(); + Button? pressedButton = assigner.GetPressedButton(); if (_shouldUnbind) { - SetButtonText(ToggledButton, "Unbound"); - } - else if (pressedButton != "") - { - SetButtonText(ToggledButton, pressedButton); + pressedButton = null; } _shouldUnbind = false; @@ -94,17 +87,8 @@ namespace Ryujinx.Ava.UI.Helpers ToggledButton.IsChecked = false; - ButtonAssigned?.Invoke(this, new ButtonAssignedEventArgs(ToggledButton, pressedButton != null)); + ButtonAssigned?.Invoke(this, new ButtonAssignedEventArgs(ToggledButton, pressedButton)); - static void SetButtonText(ToggleButton button, string text) - { - ILogical textBlock = button.GetLogicalDescendants().First(x => x is TextBlock); - - if (textBlock != null && textBlock is TextBlock block) - { - block.Text = text; - } - } }); } diff --git a/src/Ryujinx/UI/Helpers/KeyValueConverter.cs b/src/Ryujinx/UI/Helpers/KeyValueConverter.cs index 028ed6bf46..cbcf16ab32 100644 --- a/src/Ryujinx/UI/Helpers/KeyValueConverter.cs +++ b/src/Ryujinx/UI/Helpers/KeyValueConverter.cs @@ -1,7 +1,9 @@ using Avalonia.Data.Converters; +using Ryujinx.Ava.Common.Locale; using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid.Controller; using System; +using System.Collections.Generic; using System.Globalization; namespace Ryujinx.Ava.UI.Helpers @@ -10,37 +12,173 @@ namespace Ryujinx.Ava.UI.Helpers { public static KeyValueConverter Instance = new(); + private static readonly Dictionary _keysMap = new() + { + { Key.Unknown, LocaleKeys.KeyUnknown }, + { Key.ShiftLeft, LocaleKeys.KeyShiftLeft }, + { Key.ShiftRight, LocaleKeys.KeyShiftRight }, + { Key.ControlLeft, LocaleKeys.KeyControlLeft }, + { Key.ControlRight, LocaleKeys.KeyControlRight }, + { Key.AltLeft, LocaleKeys.KeyControlLeft }, + { Key.AltRight, LocaleKeys.KeyControlRight }, + { Key.WinLeft, LocaleKeys.KeyWinLeft }, + { Key.WinRight, LocaleKeys.KeyWinRight }, + { Key.Up, LocaleKeys.KeyUp }, + { Key.Down, LocaleKeys.KeyDown }, + { Key.Left, LocaleKeys.KeyLeft }, + { Key.Right, LocaleKeys.KeyRight }, + { Key.Enter, LocaleKeys.KeyEnter }, + { Key.Escape, LocaleKeys.KeyEscape }, + { Key.Space, LocaleKeys.KeySpace }, + { Key.Tab, LocaleKeys.KeyTab }, + { Key.BackSpace, LocaleKeys.KeyBackSpace }, + { Key.Insert, LocaleKeys.KeyInsert }, + { Key.Delete, LocaleKeys.KeyDelete }, + { Key.PageUp, LocaleKeys.KeyPageUp }, + { Key.PageDown, LocaleKeys.KeyPageDown }, + { Key.Home, LocaleKeys.KeyHome }, + { Key.End, LocaleKeys.KeyEnd }, + { Key.CapsLock, LocaleKeys.KeyCapsLock }, + { Key.ScrollLock, LocaleKeys.KeyScrollLock }, + { Key.PrintScreen, LocaleKeys.KeyPrintScreen }, + { Key.Pause, LocaleKeys.KeyPause }, + { Key.NumLock, LocaleKeys.KeyNumLock }, + { Key.Clear, LocaleKeys.KeyClear }, + { Key.Keypad0, LocaleKeys.KeyKeypad0 }, + { Key.Keypad1, LocaleKeys.KeyKeypad1 }, + { Key.Keypad2, LocaleKeys.KeyKeypad2 }, + { Key.Keypad3, LocaleKeys.KeyKeypad3 }, + { Key.Keypad4, LocaleKeys.KeyKeypad4 }, + { Key.Keypad5, LocaleKeys.KeyKeypad5 }, + { Key.Keypad6, LocaleKeys.KeyKeypad6 }, + { Key.Keypad7, LocaleKeys.KeyKeypad7 }, + { Key.Keypad8, LocaleKeys.KeyKeypad8 }, + { Key.Keypad9, LocaleKeys.KeyKeypad9 }, + { Key.KeypadDivide, LocaleKeys.KeyKeypadDivide }, + { Key.KeypadMultiply, LocaleKeys.KeyKeypadMultiply }, + { Key.KeypadSubtract, LocaleKeys.KeyKeypadSubtract }, + { Key.KeypadAdd, LocaleKeys.KeyKeypadAdd }, + { Key.KeypadDecimal, LocaleKeys.KeyKeypadDecimal }, + { Key.KeypadEnter, LocaleKeys.KeyKeypadEnter }, + { Key.Number0, LocaleKeys.KeyNumber0 }, + { Key.Number1, LocaleKeys.KeyNumber1 }, + { Key.Number2, LocaleKeys.KeyNumber2 }, + { Key.Number3, LocaleKeys.KeyNumber3 }, + { Key.Number4, LocaleKeys.KeyNumber4 }, + { Key.Number5, LocaleKeys.KeyNumber5 }, + { Key.Number6, LocaleKeys.KeyNumber6 }, + { Key.Number7, LocaleKeys.KeyNumber7 }, + { Key.Number8, LocaleKeys.KeyNumber8 }, + { Key.Number9, LocaleKeys.KeyNumber9 }, + { Key.Tilde, LocaleKeys.KeyTilde }, + { Key.Grave, LocaleKeys.KeyGrave }, + { Key.Minus, LocaleKeys.KeyMinus }, + { Key.Plus, LocaleKeys.KeyPlus }, + { Key.BracketLeft, LocaleKeys.KeyBracketLeft }, + { Key.BracketRight, LocaleKeys.KeyBracketRight }, + { Key.Semicolon, LocaleKeys.KeySemicolon }, + { Key.Quote, LocaleKeys.KeyQuote }, + { Key.Comma, LocaleKeys.KeyComma }, + { Key.Period, LocaleKeys.KeyPeriod }, + { Key.Slash, LocaleKeys.KeySlash }, + { Key.BackSlash, LocaleKeys.KeyBackSlash }, + { Key.Unbound, LocaleKeys.KeyUnbound }, + }; + + private static readonly Dictionary _gamepadInputIdMap = new() + { + { GamepadInputId.LeftStick, LocaleKeys.GamepadLeftStick }, + { GamepadInputId.RightStick, LocaleKeys.GamepadRightStick }, + { GamepadInputId.LeftShoulder, LocaleKeys.GamepadLeftShoulder }, + { GamepadInputId.RightShoulder, LocaleKeys.GamepadRightShoulder }, + { GamepadInputId.LeftTrigger, LocaleKeys.GamepadLeftTrigger }, + { GamepadInputId.RightTrigger, LocaleKeys.GamepadRightTrigger }, + { GamepadInputId.DpadUp, LocaleKeys.GamepadDpadUp}, + { GamepadInputId.DpadDown, LocaleKeys.GamepadDpadDown}, + { GamepadInputId.DpadLeft, LocaleKeys.GamepadDpadLeft}, + { GamepadInputId.DpadRight, LocaleKeys.GamepadDpadRight}, + { GamepadInputId.Minus, LocaleKeys.GamepadMinus}, + { GamepadInputId.Plus, LocaleKeys.GamepadPlus}, + { GamepadInputId.Guide, LocaleKeys.GamepadGuide}, + { GamepadInputId.Misc1, LocaleKeys.GamepadMisc1}, + { GamepadInputId.Paddle1, LocaleKeys.GamepadPaddle1}, + { GamepadInputId.Paddle2, LocaleKeys.GamepadPaddle2}, + { GamepadInputId.Paddle3, LocaleKeys.GamepadPaddle3}, + { GamepadInputId.Paddle4, LocaleKeys.GamepadPaddle4}, + { GamepadInputId.Touchpad, LocaleKeys.GamepadTouchpad}, + { GamepadInputId.SingleLeftTrigger0, LocaleKeys.GamepadSingleLeftTrigger0}, + { GamepadInputId.SingleRightTrigger0, LocaleKeys.GamepadSingleRightTrigger0}, + { GamepadInputId.SingleLeftTrigger1, LocaleKeys.GamepadSingleLeftTrigger1}, + { GamepadInputId.SingleRightTrigger1, LocaleKeys.GamepadSingleRightTrigger1}, + { GamepadInputId.Unbound, LocaleKeys.KeyUnbound}, + }; + + private static readonly Dictionary _stickInputIdMap = new() + { + { StickInputId.Left, LocaleKeys.StickLeft}, + { StickInputId.Right, LocaleKeys.StickRight}, + { StickInputId.Unbound, LocaleKeys.KeyUnbound}, + }; + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - if (value == null) + string keyString = ""; + LocaleKeys localeKey; + + switch (value) { - return null; + case Key key: + if (_keysMap.TryGetValue(key, out localeKey)) + { + if (OperatingSystem.IsMacOS()) + { + localeKey = localeKey switch + { + LocaleKeys.KeyControlLeft => LocaleKeys.KeyMacControlLeft, + LocaleKeys.KeyControlRight => LocaleKeys.KeyMacControlRight, + LocaleKeys.KeyAltLeft => LocaleKeys.KeyMacAltLeft, + LocaleKeys.KeyAltRight => LocaleKeys.KeyMacAltRight, + LocaleKeys.KeyWinLeft => LocaleKeys.KeyMacWinLeft, + LocaleKeys.KeyWinRight => LocaleKeys.KeyMacWinRight, + _ => localeKey + }; + } + + keyString = LocaleManager.Instance[localeKey]; + } + else + { + keyString = key.ToString(); + } + break; + case GamepadInputId gamepadInputId: + if (_gamepadInputIdMap.TryGetValue(gamepadInputId, out localeKey)) + { + keyString = LocaleManager.Instance[localeKey]; + } + else + { + keyString = gamepadInputId.ToString(); + } + break; + case StickInputId stickInputId: + if (_stickInputIdMap.TryGetValue(stickInputId, out localeKey)) + { + keyString = LocaleManager.Instance[localeKey]; + } + else + { + keyString = stickInputId.ToString(); + } + break; } - return value.ToString(); + return keyString; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { - object key = null; - - if (value != null) - { - if (targetType == typeof(Key)) - { - key = Enum.Parse(value.ToString()); - } - else if (targetType == typeof(GamepadInputId)) - { - key = Enum.Parse(value.ToString()); - } - else if (targetType == typeof(StickInputId)) - { - key = Enum.Parse(value.ToString()); - } - } - - return key; + throw new NotSupportedException(); } } } diff --git a/src/Ryujinx/UI/Models/Input/GamepadInputConfig.cs b/src/Ryujinx/UI/Models/Input/GamepadInputConfig.cs new file mode 100644 index 0000000000..833670bdc4 --- /dev/null +++ b/src/Ryujinx/UI/Models/Input/GamepadInputConfig.cs @@ -0,0 +1,580 @@ +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Common.Configuration.Hid.Controller.Motion; +using System; + +namespace Ryujinx.Ava.UI.Models.Input +{ + public class GamepadInputConfig : BaseModel + { + public bool EnableCemuHookMotion { get; set; } + public string DsuServerHost { get; set; } + public int DsuServerPort { get; set; } + public int Slot { get; set; } + public int AltSlot { get; set; } + public bool MirrorInput { get; set; } + public int Sensitivity { get; set; } + public double GyroDeadzone { get; set; } + + public float WeakRumble { get; set; } + public float StrongRumble { get; set; } + + public string Id { get; set; } + public ControllerType ControllerType { get; set; } + public PlayerIndex PlayerIndex { get; set; } + + private StickInputId _leftJoystick; + public StickInputId LeftJoystick + { + get => _leftJoystick; + set + { + _leftJoystick = value; + OnPropertyChanged(); + } + } + + private bool _leftInvertStickX; + public bool LeftInvertStickX + { + get => _leftInvertStickX; + set + { + _leftInvertStickX = value; + OnPropertyChanged(); + } + } + + private bool _leftInvertStickY; + public bool LeftInvertStickY + { + get => _leftInvertStickY; + set + { + _leftInvertStickY = value; + OnPropertyChanged(); + } + } + + private bool _leftRotate90; + public bool LeftRotate90 + { + get => _leftRotate90; + set + { + _leftRotate90 = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _leftStickButton; + public GamepadInputId LeftStickButton + { + get => _leftStickButton; + set + { + _leftStickButton = value; + OnPropertyChanged(); + } + } + + private StickInputId _rightJoystick; + public StickInputId RightJoystick + { + get => _rightJoystick; + set + { + _rightJoystick = value; + OnPropertyChanged(); + } + } + + private bool _rightInvertStickX; + public bool RightInvertStickX + { + get => _rightInvertStickX; + set + { + _rightInvertStickX = value; + OnPropertyChanged(); + } + } + + private bool _rightInvertStickY; + public bool RightInvertStickY + { + get => _rightInvertStickY; + set + { + _rightInvertStickY = value; + OnPropertyChanged(); + } + } + + private bool _rightRotate90; + public bool RightRotate90 + { + get => _rightRotate90; + set + { + _rightRotate90 = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _rightStickButton; + public GamepadInputId RightStickButton + { + get => _rightStickButton; + set + { + _rightStickButton = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _dpadUp; + public GamepadInputId DpadUp + { + get => _dpadUp; + set + { + _dpadUp = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _dpadDown; + public GamepadInputId DpadDown + { + get => _dpadDown; + set + { + _dpadDown = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _dpadLeft; + public GamepadInputId DpadLeft + { + get => _dpadLeft; + set + { + _dpadLeft = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _dpadRight; + public GamepadInputId DpadRight + { + get => _dpadRight; + set + { + _dpadRight = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _buttonL; + public GamepadInputId ButtonL + { + get => _buttonL; + set + { + _buttonL = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _buttonMinus; + public GamepadInputId ButtonMinus + { + get => _buttonMinus; + set + { + _buttonMinus = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _leftButtonSl; + public GamepadInputId LeftButtonSl + { + get => _leftButtonSl; + set + { + _leftButtonSl = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _leftButtonSr; + public GamepadInputId LeftButtonSr + { + get => _leftButtonSr; + set + { + _leftButtonSr = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _buttonZl; + public GamepadInputId ButtonZl + { + get => _buttonZl; + set + { + _buttonZl = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _buttonA; + public GamepadInputId ButtonA + { + get => _buttonA; + set + { + _buttonA = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _buttonB; + public GamepadInputId ButtonB + { + get => _buttonB; + set + { + _buttonB = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _buttonX; + public GamepadInputId ButtonX + { + get => _buttonX; + set + { + _buttonX = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _buttonY; + public GamepadInputId ButtonY + { + get => _buttonY; + set + { + _buttonY = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _buttonR; + public GamepadInputId ButtonR + { + get => _buttonR; + set + { + _buttonR = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _buttonPlus; + public GamepadInputId ButtonPlus + { + get => _buttonPlus; + set + { + _buttonPlus = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _rightButtonSl; + public GamepadInputId RightButtonSl + { + get => _rightButtonSl; + set + { + _rightButtonSl = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _rightButtonSr; + public GamepadInputId RightButtonSr + { + get => _rightButtonSr; + set + { + _rightButtonSr = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _buttonZr; + public GamepadInputId ButtonZr + { + get => _buttonZr; + set + { + _buttonZr = value; + OnPropertyChanged(); + } + } + + private float _deadzoneLeft; + public float DeadzoneLeft + { + get => _deadzoneLeft; + set + { + _deadzoneLeft = MathF.Round(value, 3); + OnPropertyChanged(); + } + } + + private float _deadzoneRight; + public float DeadzoneRight + { + get => _deadzoneRight; + set + { + _deadzoneRight = MathF.Round(value, 3); + OnPropertyChanged(); + } + } + + private float _rangeLeft; + public float RangeLeft + { + get => _rangeLeft; + set + { + _rangeLeft = MathF.Round(value, 3); + OnPropertyChanged(); + } + } + + private float _rangeRight; + public float RangeRight + { + get => _rangeRight; + set + { + _rangeRight = MathF.Round(value, 3); + OnPropertyChanged(); + } + } + + private float _triggerThreshold; + public float TriggerThreshold + { + get => _triggerThreshold; + set + { + _triggerThreshold = MathF.Round(value, 3); + OnPropertyChanged(); + } + } + + private bool _enableMotion; + public bool EnableMotion + { + get => _enableMotion; + set + { + _enableMotion = value; + OnPropertyChanged(); + } + } + + private bool _enableRumble; + public bool EnableRumble + { + get => _enableRumble; + set + { + _enableRumble = value; + OnPropertyChanged(); + } + } + + public GamepadInputConfig(InputConfig config) + { + if (config != null) + { + Id = config.Id; + ControllerType = config.ControllerType; + PlayerIndex = config.PlayerIndex; + + if (config is not StandardControllerInputConfig controllerInput) + { + return; + } + + LeftJoystick = controllerInput.LeftJoyconStick.Joystick; + LeftInvertStickX = controllerInput.LeftJoyconStick.InvertStickX; + LeftInvertStickY = controllerInput.LeftJoyconStick.InvertStickY; + LeftRotate90 = controllerInput.LeftJoyconStick.Rotate90CW; + LeftStickButton = controllerInput.LeftJoyconStick.StickButton; + + RightJoystick = controllerInput.RightJoyconStick.Joystick; + RightInvertStickX = controllerInput.RightJoyconStick.InvertStickX; + RightInvertStickY = controllerInput.RightJoyconStick.InvertStickY; + RightRotate90 = controllerInput.RightJoyconStick.Rotate90CW; + RightStickButton = controllerInput.RightJoyconStick.StickButton; + + DpadUp = controllerInput.LeftJoycon.DpadUp; + DpadDown = controllerInput.LeftJoycon.DpadDown; + DpadLeft = controllerInput.LeftJoycon.DpadLeft; + DpadRight = controllerInput.LeftJoycon.DpadRight; + ButtonL = controllerInput.LeftJoycon.ButtonL; + ButtonMinus = controllerInput.LeftJoycon.ButtonMinus; + LeftButtonSl = controllerInput.LeftJoycon.ButtonSl; + LeftButtonSr = controllerInput.LeftJoycon.ButtonSr; + ButtonZl = controllerInput.LeftJoycon.ButtonZl; + + ButtonA = controllerInput.RightJoycon.ButtonA; + ButtonB = controllerInput.RightJoycon.ButtonB; + ButtonX = controllerInput.RightJoycon.ButtonX; + ButtonY = controllerInput.RightJoycon.ButtonY; + ButtonR = controllerInput.RightJoycon.ButtonR; + ButtonPlus = controllerInput.RightJoycon.ButtonPlus; + RightButtonSl = controllerInput.RightJoycon.ButtonSl; + RightButtonSr = controllerInput.RightJoycon.ButtonSr; + ButtonZr = controllerInput.RightJoycon.ButtonZr; + + DeadzoneLeft = controllerInput.DeadzoneLeft; + DeadzoneRight = controllerInput.DeadzoneRight; + RangeLeft = controllerInput.RangeLeft; + RangeRight = controllerInput.RangeRight; + TriggerThreshold = controllerInput.TriggerThreshold; + + if (controllerInput.Motion != null) + { + EnableMotion = controllerInput.Motion.EnableMotion; + GyroDeadzone = controllerInput.Motion.GyroDeadzone; + Sensitivity = controllerInput.Motion.Sensitivity; + + if (controllerInput.Motion is CemuHookMotionConfigController cemuHook) + { + EnableCemuHookMotion = true; + DsuServerHost = cemuHook.DsuServerHost; + DsuServerPort = cemuHook.DsuServerPort; + Slot = cemuHook.Slot; + AltSlot = cemuHook.AltSlot; + MirrorInput = cemuHook.MirrorInput; + } + } + + if (controllerInput.Rumble != null) + { + EnableRumble = controllerInput.Rumble.EnableRumble; + WeakRumble = controllerInput.Rumble.WeakRumble; + StrongRumble = controllerInput.Rumble.StrongRumble; + } + } + } + + public InputConfig GetConfig() + { + var config = new StandardControllerInputConfig + { + Id = Id, + Backend = InputBackendType.GamepadSDL2, + PlayerIndex = PlayerIndex, + ControllerType = ControllerType, + LeftJoycon = new LeftJoyconCommonConfig + { + DpadUp = DpadUp, + DpadDown = DpadDown, + DpadLeft = DpadLeft, + DpadRight = DpadRight, + ButtonL = ButtonL, + ButtonMinus = ButtonMinus, + ButtonSl = LeftButtonSl, + ButtonSr = LeftButtonSr, + ButtonZl = ButtonZl, + }, + RightJoycon = new RightJoyconCommonConfig + { + ButtonA = ButtonA, + ButtonB = ButtonB, + ButtonX = ButtonX, + ButtonY = ButtonY, + ButtonPlus = ButtonPlus, + ButtonSl = RightButtonSl, + ButtonSr = RightButtonSr, + ButtonR = ButtonR, + ButtonZr = ButtonZr, + }, + LeftJoyconStick = new JoyconConfigControllerStick + { + Joystick = LeftJoystick, + InvertStickX = LeftInvertStickX, + InvertStickY = LeftInvertStickY, + Rotate90CW = LeftRotate90, + StickButton = LeftStickButton, + }, + RightJoyconStick = new JoyconConfigControllerStick + { + Joystick = RightJoystick, + InvertStickX = RightInvertStickX, + InvertStickY = RightInvertStickY, + Rotate90CW = RightRotate90, + StickButton = RightStickButton, + }, + Rumble = new RumbleConfigController + { + EnableRumble = EnableRumble, + WeakRumble = WeakRumble, + StrongRumble = StrongRumble, + }, + Version = InputConfig.CurrentVersion, + DeadzoneLeft = DeadzoneLeft, + DeadzoneRight = DeadzoneRight, + RangeLeft = RangeLeft, + RangeRight = RangeRight, + TriggerThreshold = TriggerThreshold, + }; + + if (EnableCemuHookMotion) + { + config.Motion = new CemuHookMotionConfigController + { + EnableMotion = EnableMotion, + MotionBackend = MotionInputBackendType.CemuHook, + GyroDeadzone = GyroDeadzone, + Sensitivity = Sensitivity, + DsuServerHost = DsuServerHost, + DsuServerPort = DsuServerPort, + Slot = Slot, + AltSlot = AltSlot, + MirrorInput = MirrorInput, + }; + } + else + { + config.Motion = new StandardMotionConfigController + { + EnableMotion = EnableMotion, + MotionBackend = MotionInputBackendType.GamepadDriver, + GyroDeadzone = GyroDeadzone, + Sensitivity = Sensitivity, + }; + } + + return config; + } + } +} diff --git a/src/Ryujinx/UI/Models/Input/HotkeyConfig.cs b/src/Ryujinx/UI/Models/Input/HotkeyConfig.cs new file mode 100644 index 0000000000..b5f53508bd --- /dev/null +++ b/src/Ryujinx/UI/Models/Input/HotkeyConfig.cs @@ -0,0 +1,141 @@ +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Common.Configuration.Hid; + +namespace Ryujinx.Ava.UI.Models.Input +{ + public class HotkeyConfig : BaseModel + { + private Key _toggleVsync; + public Key ToggleVsync + { + get => _toggleVsync; + set + { + _toggleVsync = value; + OnPropertyChanged(); + } + } + + private Key _screenshot; + public Key Screenshot + { + get => _screenshot; + set + { + _screenshot = value; + OnPropertyChanged(); + } + } + + private Key _showUI; + public Key ShowUI + { + get => _showUI; + set + { + _showUI = value; + OnPropertyChanged(); + } + } + + private Key _pause; + public Key Pause + { + get => _pause; + set + { + _pause = value; + OnPropertyChanged(); + } + } + + private Key _toggleMute; + public Key ToggleMute + { + get => _toggleMute; + set + { + _toggleMute = value; + OnPropertyChanged(); + } + } + + private Key _resScaleUp; + public Key ResScaleUp + { + get => _resScaleUp; + set + { + _resScaleUp = value; + OnPropertyChanged(); + } + } + + private Key _resScaleDown; + public Key ResScaleDown + { + get => _resScaleDown; + set + { + _resScaleDown = value; + OnPropertyChanged(); + } + } + + private Key _volumeUp; + public Key VolumeUp + { + get => _volumeUp; + set + { + _volumeUp = value; + OnPropertyChanged(); + } + } + + private Key _volumeDown; + public Key VolumeDown + { + get => _volumeDown; + set + { + _volumeDown = value; + OnPropertyChanged(); + } + } + + public HotkeyConfig(KeyboardHotkeys config) + { + if (config != null) + { + ToggleVsync = config.ToggleVsync; + Screenshot = config.Screenshot; + ShowUI = config.ShowUI; + Pause = config.Pause; + ToggleMute = config.ToggleMute; + ResScaleUp = config.ResScaleUp; + ResScaleDown = config.ResScaleDown; + VolumeUp = config.VolumeUp; + VolumeDown = config.VolumeDown; + } + } + + public KeyboardHotkeys GetConfig() + { + var config = new KeyboardHotkeys + { + ToggleVsync = ToggleVsync, + Screenshot = Screenshot, + ShowUI = ShowUI, + Pause = Pause, + ToggleMute = ToggleMute, + ResScaleUp = ResScaleUp, + ResScaleDown = ResScaleDown, + VolumeUp = VolumeUp, + VolumeDown = VolumeDown, + }; + + return config; + } + } +} diff --git a/src/Ryujinx/UI/Models/Input/KeyboardInputConfig.cs b/src/Ryujinx/UI/Models/Input/KeyboardInputConfig.cs new file mode 100644 index 0000000000..66f1f62a22 --- /dev/null +++ b/src/Ryujinx/UI/Models/Input/KeyboardInputConfig.cs @@ -0,0 +1,422 @@ +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Keyboard; + +namespace Ryujinx.Ava.UI.Models.Input +{ + public class KeyboardInputConfig : BaseModel + { + public string Id { get; set; } + public ControllerType ControllerType { get; set; } + public PlayerIndex PlayerIndex { get; set; } + + private Key _leftStickUp; + public Key LeftStickUp + { + get => _leftStickUp; + set + { + _leftStickUp = value; + OnPropertyChanged(); + } + } + + private Key _leftStickDown; + public Key LeftStickDown + { + get => _leftStickDown; + set + { + _leftStickDown = value; + OnPropertyChanged(); + } + } + + private Key _leftStickLeft; + public Key LeftStickLeft + { + get => _leftStickLeft; + set + { + _leftStickLeft = value; + OnPropertyChanged(); + } + } + + private Key _leftStickRight; + public Key LeftStickRight + { + get => _leftStickRight; + set + { + _leftStickRight = value; + OnPropertyChanged(); + } + } + + private Key _leftStickButton; + public Key LeftStickButton + { + get => _leftStickButton; + set + { + _leftStickButton = value; + OnPropertyChanged(); + } + } + + private Key _rightStickUp; + public Key RightStickUp + { + get => _rightStickUp; + set + { + _rightStickUp = value; + OnPropertyChanged(); + } + } + + private Key _rightStickDown; + public Key RightStickDown + { + get => _rightStickDown; + set + { + _rightStickDown = value; + OnPropertyChanged(); + } + } + + private Key _rightStickLeft; + public Key RightStickLeft + { + get => _rightStickLeft; + set + { + _rightStickLeft = value; + OnPropertyChanged(); + } + } + + private Key _rightStickRight; + public Key RightStickRight + { + get => _rightStickRight; + set + { + _rightStickRight = value; + OnPropertyChanged(); + } + } + + private Key _rightStickButton; + public Key RightStickButton + { + get => _rightStickButton; + set + { + _rightStickButton = value; + OnPropertyChanged(); + } + } + + private Key _dpadUp; + public Key DpadUp + { + get => _dpadUp; + set + { + _dpadUp = value; + OnPropertyChanged(); + } + } + + private Key _dpadDown; + public Key DpadDown + { + get => _dpadDown; + set + { + _dpadDown = value; + OnPropertyChanged(); + } + } + + private Key _dpadLeft; + public Key DpadLeft + { + get => _dpadLeft; + set + { + _dpadLeft = value; + OnPropertyChanged(); + } + } + + private Key _dpadRight; + public Key DpadRight + { + get => _dpadRight; + set + { + _dpadRight = value; + OnPropertyChanged(); + } + } + + private Key _buttonL; + public Key ButtonL + { + get => _buttonL; + set + { + _buttonL = value; + OnPropertyChanged(); + } + } + + private Key _buttonMinus; + public Key ButtonMinus + { + get => _buttonMinus; + set + { + _buttonMinus = value; + OnPropertyChanged(); + } + } + + private Key _leftButtonSl; + public Key LeftButtonSl + { + get => _leftButtonSl; + set + { + _leftButtonSl = value; + OnPropertyChanged(); + } + } + + private Key _leftButtonSr; + public Key LeftButtonSr + { + get => _leftButtonSr; + set + { + _leftButtonSr = value; + OnPropertyChanged(); + } + } + + private Key _buttonZl; + public Key ButtonZl + { + get => _buttonZl; + set + { + _buttonZl = value; + OnPropertyChanged(); + } + } + + private Key _buttonA; + public Key ButtonA + { + get => _buttonA; + set + { + _buttonA = value; + OnPropertyChanged(); + } + } + + private Key _buttonB; + public Key ButtonB + { + get => _buttonB; + set + { + _buttonB = value; + OnPropertyChanged(); + } + } + + private Key _buttonX; + public Key ButtonX + { + get => _buttonX; + set + { + _buttonX = value; + OnPropertyChanged(); + } + } + + private Key _buttonY; + public Key ButtonY + { + get => _buttonY; + set + { + _buttonY = value; + OnPropertyChanged(); + } + } + + private Key _buttonR; + public Key ButtonR + { + get => _buttonR; + set + { + _buttonR = value; + OnPropertyChanged(); + } + } + + private Key _buttonPlus; + public Key ButtonPlus + { + get => _buttonPlus; + set + { + _buttonPlus = value; + OnPropertyChanged(); + } + } + + private Key _rightButtonSl; + public Key RightButtonSl + { + get => _rightButtonSl; + set + { + _rightButtonSl = value; + OnPropertyChanged(); + } + } + + private Key _rightButtonSr; + public Key RightButtonSr + { + get => _rightButtonSr; + set + { + _rightButtonSr = value; + OnPropertyChanged(); + } + } + + private Key _buttonZr; + public Key ButtonZr + { + get => _buttonZr; + set + { + _buttonZr = value; + OnPropertyChanged(); + } + } + + public KeyboardInputConfig(InputConfig config) + { + if (config != null) + { + Id = config.Id; + ControllerType = config.ControllerType; + PlayerIndex = config.PlayerIndex; + + if (config is not StandardKeyboardInputConfig keyboardConfig) + { + return; + } + + LeftStickUp = keyboardConfig.LeftJoyconStick.StickUp; + LeftStickDown = keyboardConfig.LeftJoyconStick.StickDown; + LeftStickLeft = keyboardConfig.LeftJoyconStick.StickLeft; + LeftStickRight = keyboardConfig.LeftJoyconStick.StickRight; + LeftStickButton = keyboardConfig.LeftJoyconStick.StickButton; + + RightStickUp = keyboardConfig.RightJoyconStick.StickUp; + RightStickDown = keyboardConfig.RightJoyconStick.StickDown; + RightStickLeft = keyboardConfig.RightJoyconStick.StickLeft; + RightStickRight = keyboardConfig.RightJoyconStick.StickRight; + RightStickButton = keyboardConfig.RightJoyconStick.StickButton; + + DpadUp = keyboardConfig.LeftJoycon.DpadUp; + DpadDown = keyboardConfig.LeftJoycon.DpadDown; + DpadLeft = keyboardConfig.LeftJoycon.DpadLeft; + DpadRight = keyboardConfig.LeftJoycon.DpadRight; + ButtonL = keyboardConfig.LeftJoycon.ButtonL; + ButtonMinus = keyboardConfig.LeftJoycon.ButtonMinus; + LeftButtonSl = keyboardConfig.LeftJoycon.ButtonSl; + LeftButtonSr = keyboardConfig.LeftJoycon.ButtonSr; + ButtonZl = keyboardConfig.LeftJoycon.ButtonZl; + + ButtonA = keyboardConfig.RightJoycon.ButtonA; + ButtonB = keyboardConfig.RightJoycon.ButtonB; + ButtonX = keyboardConfig.RightJoycon.ButtonX; + ButtonY = keyboardConfig.RightJoycon.ButtonY; + ButtonR = keyboardConfig.RightJoycon.ButtonR; + ButtonPlus = keyboardConfig.RightJoycon.ButtonPlus; + RightButtonSl = keyboardConfig.RightJoycon.ButtonSl; + RightButtonSr = keyboardConfig.RightJoycon.ButtonSr; + ButtonZr = keyboardConfig.RightJoycon.ButtonZr; + } + } + + public InputConfig GetConfig() + { + var config = new StandardKeyboardInputConfig + { + Id = Id, + Backend = InputBackendType.WindowKeyboard, + PlayerIndex = PlayerIndex, + ControllerType = ControllerType, + LeftJoycon = new LeftJoyconCommonConfig + { + DpadUp = DpadUp, + DpadDown = DpadDown, + DpadLeft = DpadLeft, + DpadRight = DpadRight, + ButtonL = ButtonL, + ButtonMinus = ButtonMinus, + ButtonZl = ButtonZl, + ButtonSl = LeftButtonSl, + ButtonSr = LeftButtonSr, + }, + RightJoycon = new RightJoyconCommonConfig + { + ButtonA = ButtonA, + ButtonB = ButtonB, + ButtonX = ButtonX, + ButtonY = ButtonY, + ButtonPlus = ButtonPlus, + ButtonSl = RightButtonSl, + ButtonSr = RightButtonSr, + ButtonR = ButtonR, + ButtonZr = ButtonZr, + }, + LeftJoyconStick = new JoyconConfigKeyboardStick + { + StickUp = LeftStickUp, + StickDown = LeftStickDown, + StickRight = LeftStickRight, + StickLeft = LeftStickLeft, + StickButton = LeftStickButton, + }, + RightJoyconStick = new JoyconConfigKeyboardStick + { + StickUp = RightStickUp, + StickDown = RightStickDown, + StickLeft = RightStickLeft, + StickRight = RightStickRight, + StickButton = RightStickButton, + }, + Version = InputConfig.CurrentVersion, + }; + + return config; + } + } +} diff --git a/src/Ryujinx/UI/Models/InputConfiguration.cs b/src/Ryujinx/UI/Models/InputConfiguration.cs deleted file mode 100644 index f1352c6d8b..0000000000 --- a/src/Ryujinx/UI/Models/InputConfiguration.cs +++ /dev/null @@ -1,456 +0,0 @@ -using Ryujinx.Ava.UI.ViewModels; -using Ryujinx.Common.Configuration.Hid; -using Ryujinx.Common.Configuration.Hid.Controller; -using Ryujinx.Common.Configuration.Hid.Controller.Motion; -using Ryujinx.Common.Configuration.Hid.Keyboard; -using System; - -namespace Ryujinx.Ava.UI.Models -{ - internal class InputConfiguration : BaseModel - { - private float _deadzoneRight; - private float _triggerThreshold; - private float _deadzoneLeft; - private double _gyroDeadzone; - private int _sensitivity; - private bool _enableMotion; - private float _weakRumble; - private float _strongRumble; - private float _rangeLeft; - private float _rangeRight; - - public InputBackendType Backend { get; set; } - - /// - /// Controller id - /// - public string Id { get; set; } - - /// - /// Controller's Type - /// - public ControllerType ControllerType { get; set; } - - /// - /// Player's Index for the controller - /// - public PlayerIndex PlayerIndex { get; set; } - - public TStick LeftJoystick { get; set; } - public bool LeftInvertStickX { get; set; } - public bool LeftInvertStickY { get; set; } - public bool RightRotate90 { get; set; } - public TKey LeftControllerStickButton { get; set; } - - public TStick RightJoystick { get; set; } - public bool RightInvertStickX { get; set; } - public bool RightInvertStickY { get; set; } - public bool LeftRotate90 { get; set; } - public TKey RightControllerStickButton { get; set; } - - public float DeadzoneLeft - { - get => _deadzoneLeft; - set - { - _deadzoneLeft = MathF.Round(value, 3); - - OnPropertyChanged(); - } - } - - public float RangeLeft - { - get => _rangeLeft; - set - { - _rangeLeft = MathF.Round(value, 3); - - OnPropertyChanged(); - } - } - - public float DeadzoneRight - { - get => _deadzoneRight; - set - { - _deadzoneRight = MathF.Round(value, 3); - - OnPropertyChanged(); - } - } - - public float RangeRight - { - get => _rangeRight; - set - { - _rangeRight = MathF.Round(value, 3); - - OnPropertyChanged(); - } - } - - public float TriggerThreshold - { - get => _triggerThreshold; - set - { - _triggerThreshold = MathF.Round(value, 3); - - OnPropertyChanged(); - } - } - - public MotionInputBackendType MotionBackend { get; set; } - - public TKey ButtonMinus { get; set; } - public TKey ButtonL { get; set; } - public TKey ButtonZl { get; set; } - public TKey LeftButtonSl { get; set; } - public TKey LeftButtonSr { get; set; } - public TKey DpadUp { get; set; } - public TKey DpadDown { get; set; } - public TKey DpadLeft { get; set; } - public TKey DpadRight { get; set; } - - public TKey ButtonPlus { get; set; } - public TKey ButtonR { get; set; } - public TKey ButtonZr { get; set; } - public TKey RightButtonSl { get; set; } - public TKey RightButtonSr { get; set; } - public TKey ButtonX { get; set; } - public TKey ButtonB { get; set; } - public TKey ButtonY { get; set; } - public TKey ButtonA { get; set; } - - public TKey LeftStickUp { get; set; } - public TKey LeftStickDown { get; set; } - public TKey LeftStickLeft { get; set; } - public TKey LeftStickRight { get; set; } - public TKey LeftKeyboardStickButton { get; set; } - - public TKey RightStickUp { get; set; } - public TKey RightStickDown { get; set; } - public TKey RightStickLeft { get; set; } - public TKey RightStickRight { get; set; } - public TKey RightKeyboardStickButton { get; set; } - - public int Sensitivity - { - get => _sensitivity; - set - { - _sensitivity = value; - - OnPropertyChanged(); - } - } - - public double GyroDeadzone - { - get => _gyroDeadzone; - set - { - _gyroDeadzone = Math.Round(value, 3); - - OnPropertyChanged(); - } - } - - public bool EnableMotion - { - get => _enableMotion; set - { - _enableMotion = value; - - OnPropertyChanged(); - } - } - - public bool EnableCemuHookMotion { get; set; } - public int Slot { get; set; } - public int AltSlot { get; set; } - public bool MirrorInput { get; set; } - public string DsuServerHost { get; set; } - public int DsuServerPort { get; set; } - - public bool EnableRumble { get; set; } - public float WeakRumble - { - get => _weakRumble; set - { - _weakRumble = value; - - OnPropertyChanged(); - } - } - public float StrongRumble - { - get => _strongRumble; set - { - _strongRumble = value; - - OnPropertyChanged(); - } - } - - public InputConfiguration(InputConfig config) - { - if (config != null) - { - Backend = config.Backend; - Id = config.Id; - ControllerType = config.ControllerType; - PlayerIndex = config.PlayerIndex; - - if (config is StandardKeyboardInputConfig keyboardConfig) - { - LeftStickUp = (TKey)(object)keyboardConfig.LeftJoyconStick.StickUp; - LeftStickDown = (TKey)(object)keyboardConfig.LeftJoyconStick.StickDown; - LeftStickLeft = (TKey)(object)keyboardConfig.LeftJoyconStick.StickLeft; - LeftStickRight = (TKey)(object)keyboardConfig.LeftJoyconStick.StickRight; - LeftKeyboardStickButton = (TKey)(object)keyboardConfig.LeftJoyconStick.StickButton; - - RightStickUp = (TKey)(object)keyboardConfig.RightJoyconStick.StickUp; - RightStickDown = (TKey)(object)keyboardConfig.RightJoyconStick.StickDown; - RightStickLeft = (TKey)(object)keyboardConfig.RightJoyconStick.StickLeft; - RightStickRight = (TKey)(object)keyboardConfig.RightJoyconStick.StickRight; - RightKeyboardStickButton = (TKey)(object)keyboardConfig.RightJoyconStick.StickButton; - - ButtonA = (TKey)(object)keyboardConfig.RightJoycon.ButtonA; - ButtonB = (TKey)(object)keyboardConfig.RightJoycon.ButtonB; - ButtonX = (TKey)(object)keyboardConfig.RightJoycon.ButtonX; - ButtonY = (TKey)(object)keyboardConfig.RightJoycon.ButtonY; - ButtonR = (TKey)(object)keyboardConfig.RightJoycon.ButtonR; - RightButtonSl = (TKey)(object)keyboardConfig.RightJoycon.ButtonSl; - RightButtonSr = (TKey)(object)keyboardConfig.RightJoycon.ButtonSr; - ButtonZr = (TKey)(object)keyboardConfig.RightJoycon.ButtonZr; - ButtonPlus = (TKey)(object)keyboardConfig.RightJoycon.ButtonPlus; - - DpadUp = (TKey)(object)keyboardConfig.LeftJoycon.DpadUp; - DpadDown = (TKey)(object)keyboardConfig.LeftJoycon.DpadDown; - DpadLeft = (TKey)(object)keyboardConfig.LeftJoycon.DpadLeft; - DpadRight = (TKey)(object)keyboardConfig.LeftJoycon.DpadRight; - ButtonMinus = (TKey)(object)keyboardConfig.LeftJoycon.ButtonMinus; - LeftButtonSl = (TKey)(object)keyboardConfig.LeftJoycon.ButtonSl; - LeftButtonSr = (TKey)(object)keyboardConfig.LeftJoycon.ButtonSr; - ButtonZl = (TKey)(object)keyboardConfig.LeftJoycon.ButtonZl; - ButtonL = (TKey)(object)keyboardConfig.LeftJoycon.ButtonL; - } - else if (config is StandardControllerInputConfig controllerConfig) - { - LeftJoystick = (TStick)(object)controllerConfig.LeftJoyconStick.Joystick; - LeftInvertStickX = controllerConfig.LeftJoyconStick.InvertStickX; - LeftInvertStickY = controllerConfig.LeftJoyconStick.InvertStickY; - LeftRotate90 = controllerConfig.LeftJoyconStick.Rotate90CW; - LeftControllerStickButton = (TKey)(object)controllerConfig.LeftJoyconStick.StickButton; - - RightJoystick = (TStick)(object)controllerConfig.RightJoyconStick.Joystick; - RightInvertStickX = controllerConfig.RightJoyconStick.InvertStickX; - RightInvertStickY = controllerConfig.RightJoyconStick.InvertStickY; - RightRotate90 = controllerConfig.RightJoyconStick.Rotate90CW; - RightControllerStickButton = (TKey)(object)controllerConfig.RightJoyconStick.StickButton; - - ButtonA = (TKey)(object)controllerConfig.RightJoycon.ButtonA; - ButtonB = (TKey)(object)controllerConfig.RightJoycon.ButtonB; - ButtonX = (TKey)(object)controllerConfig.RightJoycon.ButtonX; - ButtonY = (TKey)(object)controllerConfig.RightJoycon.ButtonY; - ButtonR = (TKey)(object)controllerConfig.RightJoycon.ButtonR; - RightButtonSl = (TKey)(object)controllerConfig.RightJoycon.ButtonSl; - RightButtonSr = (TKey)(object)controllerConfig.RightJoycon.ButtonSr; - ButtonZr = (TKey)(object)controllerConfig.RightJoycon.ButtonZr; - ButtonPlus = (TKey)(object)controllerConfig.RightJoycon.ButtonPlus; - - DpadUp = (TKey)(object)controllerConfig.LeftJoycon.DpadUp; - DpadDown = (TKey)(object)controllerConfig.LeftJoycon.DpadDown; - DpadLeft = (TKey)(object)controllerConfig.LeftJoycon.DpadLeft; - DpadRight = (TKey)(object)controllerConfig.LeftJoycon.DpadRight; - ButtonMinus = (TKey)(object)controllerConfig.LeftJoycon.ButtonMinus; - LeftButtonSl = (TKey)(object)controllerConfig.LeftJoycon.ButtonSl; - LeftButtonSr = (TKey)(object)controllerConfig.LeftJoycon.ButtonSr; - ButtonZl = (TKey)(object)controllerConfig.LeftJoycon.ButtonZl; - ButtonL = (TKey)(object)controllerConfig.LeftJoycon.ButtonL; - - DeadzoneLeft = controllerConfig.DeadzoneLeft; - DeadzoneRight = controllerConfig.DeadzoneRight; - RangeLeft = controllerConfig.RangeLeft; - RangeRight = controllerConfig.RangeRight; - TriggerThreshold = controllerConfig.TriggerThreshold; - - if (controllerConfig.Motion != null) - { - EnableMotion = controllerConfig.Motion.EnableMotion; - MotionBackend = controllerConfig.Motion.MotionBackend; - GyroDeadzone = controllerConfig.Motion.GyroDeadzone; - Sensitivity = controllerConfig.Motion.Sensitivity; - - if (controllerConfig.Motion is CemuHookMotionConfigController cemuHook) - { - EnableCemuHookMotion = true; - DsuServerHost = cemuHook.DsuServerHost; - DsuServerPort = cemuHook.DsuServerPort; - Slot = cemuHook.Slot; - AltSlot = cemuHook.AltSlot; - MirrorInput = cemuHook.MirrorInput; - } - - if (controllerConfig.Rumble != null) - { - EnableRumble = controllerConfig.Rumble.EnableRumble; - WeakRumble = controllerConfig.Rumble.WeakRumble; - StrongRumble = controllerConfig.Rumble.StrongRumble; - } - } - } - } - } - - public InputConfiguration() - { - } - - public InputConfig GetConfig() - { - if (Backend == InputBackendType.WindowKeyboard) - { - return new StandardKeyboardInputConfig - { - Id = Id, - Backend = Backend, - PlayerIndex = PlayerIndex, - ControllerType = ControllerType, - LeftJoycon = new LeftJoyconCommonConfig - { - DpadUp = (Key)(object)DpadUp, - DpadDown = (Key)(object)DpadDown, - DpadLeft = (Key)(object)DpadLeft, - DpadRight = (Key)(object)DpadRight, - ButtonL = (Key)(object)ButtonL, - ButtonZl = (Key)(object)ButtonZl, - ButtonSl = (Key)(object)LeftButtonSl, - ButtonSr = (Key)(object)LeftButtonSr, - ButtonMinus = (Key)(object)ButtonMinus, - }, - RightJoycon = new RightJoyconCommonConfig - { - ButtonA = (Key)(object)ButtonA, - ButtonB = (Key)(object)ButtonB, - ButtonX = (Key)(object)ButtonX, - ButtonY = (Key)(object)ButtonY, - ButtonPlus = (Key)(object)ButtonPlus, - ButtonSl = (Key)(object)RightButtonSl, - ButtonSr = (Key)(object)RightButtonSr, - ButtonR = (Key)(object)ButtonR, - ButtonZr = (Key)(object)ButtonZr, - }, - LeftJoyconStick = new JoyconConfigKeyboardStick - { - StickUp = (Key)(object)LeftStickUp, - StickDown = (Key)(object)LeftStickDown, - StickRight = (Key)(object)LeftStickRight, - StickLeft = (Key)(object)LeftStickLeft, - StickButton = (Key)(object)LeftKeyboardStickButton, - }, - RightJoyconStick = new JoyconConfigKeyboardStick - { - StickUp = (Key)(object)RightStickUp, - StickDown = (Key)(object)RightStickDown, - StickLeft = (Key)(object)RightStickLeft, - StickRight = (Key)(object)RightStickRight, - StickButton = (Key)(object)RightKeyboardStickButton, - }, - Version = InputConfig.CurrentVersion, - }; - - } - - if (Backend == InputBackendType.GamepadSDL2) - { - var config = new StandardControllerInputConfig - { - Id = Id, - Backend = Backend, - PlayerIndex = PlayerIndex, - ControllerType = ControllerType, - LeftJoycon = new LeftJoyconCommonConfig - { - DpadUp = (GamepadInputId)(object)DpadUp, - DpadDown = (GamepadInputId)(object)DpadDown, - DpadLeft = (GamepadInputId)(object)DpadLeft, - DpadRight = (GamepadInputId)(object)DpadRight, - ButtonL = (GamepadInputId)(object)ButtonL, - ButtonZl = (GamepadInputId)(object)ButtonZl, - ButtonSl = (GamepadInputId)(object)LeftButtonSl, - ButtonSr = (GamepadInputId)(object)LeftButtonSr, - ButtonMinus = (GamepadInputId)(object)ButtonMinus, - }, - RightJoycon = new RightJoyconCommonConfig - { - ButtonA = (GamepadInputId)(object)ButtonA, - ButtonB = (GamepadInputId)(object)ButtonB, - ButtonX = (GamepadInputId)(object)ButtonX, - ButtonY = (GamepadInputId)(object)ButtonY, - ButtonPlus = (GamepadInputId)(object)ButtonPlus, - ButtonSl = (GamepadInputId)(object)RightButtonSl, - ButtonSr = (GamepadInputId)(object)RightButtonSr, - ButtonR = (GamepadInputId)(object)ButtonR, - ButtonZr = (GamepadInputId)(object)ButtonZr, - }, - LeftJoyconStick = new JoyconConfigControllerStick - { - Joystick = (StickInputId)(object)LeftJoystick, - InvertStickX = LeftInvertStickX, - InvertStickY = LeftInvertStickY, - Rotate90CW = LeftRotate90, - StickButton = (GamepadInputId)(object)LeftControllerStickButton, - }, - RightJoyconStick = new JoyconConfigControllerStick - { - Joystick = (StickInputId)(object)RightJoystick, - InvertStickX = RightInvertStickX, - InvertStickY = RightInvertStickY, - Rotate90CW = RightRotate90, - StickButton = (GamepadInputId)(object)RightControllerStickButton, - }, - Rumble = new RumbleConfigController - { - EnableRumble = EnableRumble, - WeakRumble = WeakRumble, - StrongRumble = StrongRumble, - }, - Version = InputConfig.CurrentVersion, - DeadzoneLeft = DeadzoneLeft, - DeadzoneRight = DeadzoneRight, - RangeLeft = RangeLeft, - RangeRight = RangeRight, - TriggerThreshold = TriggerThreshold, - Motion = EnableCemuHookMotion - ? new CemuHookMotionConfigController - { - DsuServerHost = DsuServerHost, - DsuServerPort = DsuServerPort, - Slot = Slot, - AltSlot = AltSlot, - MirrorInput = MirrorInput, - MotionBackend = MotionInputBackendType.CemuHook, - } - : new StandardMotionConfigController - { - MotionBackend = MotionInputBackendType.GamepadDriver, - }, - }; - - config.Motion.Sensitivity = Sensitivity; - config.Motion.EnableMotion = EnableMotion; - config.Motion.GyroDeadzone = GyroDeadzone; - - return config; - } - - return null; - } - } -} diff --git a/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs new file mode 100644 index 0000000000..6ee79a371c --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs @@ -0,0 +1,84 @@ +using Avalonia.Svg.Skia; +using Ryujinx.Ava.UI.Models.Input; +using Ryujinx.Ava.UI.Views.Input; + +namespace Ryujinx.Ava.UI.ViewModels.Input +{ + public class ControllerInputViewModel : BaseModel + { + private GamepadInputConfig _config; + public GamepadInputConfig Config + { + get => _config; + set + { + _config = value; + OnPropertyChanged(); + } + } + + private bool _isLeft; + public bool IsLeft + { + get => _isLeft; + set + { + _isLeft = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(HasSides)); + } + } + + private bool _isRight; + public bool IsRight + { + get => _isRight; + set + { + _isRight = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(HasSides)); + } + } + + public bool HasSides => IsLeft ^ IsRight; + + private SvgImage _image; + public SvgImage Image + { + get => _image; + set + { + _image = value; + OnPropertyChanged(); + } + } + + public readonly InputViewModel ParentModel; + + public ControllerInputViewModel(InputViewModel model, GamepadInputConfig config) + { + ParentModel = model; + model.NotifyChangesEvent += OnParentModelChanged; + OnParentModelChanged(); + Config = config; + } + + public async void ShowMotionConfig() + { + await MotionInputView.Show(this); + } + + public async void ShowRumbleConfig() + { + await RumbleInputView.Show(this); + } + + public void OnParentModelChanged() + { + IsLeft = ParentModel.IsLeft; + IsRight = ParentModel.IsRight; + Image = ParentModel.Image; + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/ControllerInputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs similarity index 92% rename from src/Ryujinx/UI/ViewModels/ControllerInputViewModel.cs rename to src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs index 71ad2c1278..74da459793 100644 --- a/src/Ryujinx/UI/ViewModels/ControllerInputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs @@ -8,7 +8,7 @@ using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Input; using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Models; -using Ryujinx.Ava.UI.Views.Input; +using Ryujinx.Ava.UI.Models.Input; using Ryujinx.Ava.UI.Windows; using Ryujinx.Common; using Ryujinx.Common.Configuration; @@ -30,9 +30,9 @@ using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.Gamepad using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; using Key = Ryujinx.Common.Configuration.Hid.Key; -namespace Ryujinx.Ava.UI.ViewModels +namespace Ryujinx.Ava.UI.ViewModels.Input { - public class ControllerInputViewModel : BaseModel, IDisposable + public class InputViewModel : BaseModel, IDisposable { private const string Disabled = "disabled"; private const string ProControllerResource = "Ryujinx.UI.Common/Resources/Controller_ProCon.svg"; @@ -48,7 +48,7 @@ namespace Ryujinx.Ava.UI.ViewModels private int _controllerNumber; private string _controllerImage; private int _device; - private object _configuration; + private object _configViewModel; private string _profileName; private bool _isLoaded; @@ -71,13 +71,14 @@ namespace Ryujinx.Ava.UI.ViewModels public bool IsLeft { get; set; } public bool IsModified { get; set; } + public event Action NotifyChangesEvent; - public object Configuration + public object ConfigViewModel { - get => _configuration; + get => _configViewModel; set { - _configuration = value; + _configViewModel = value; OnPropertyChanged(); } @@ -232,7 +233,7 @@ namespace Ryujinx.Ava.UI.ViewModels public InputConfig Config { get; set; } - public ControllerInputViewModel(UserControl owner) : this() + public InputViewModel(UserControl owner) : this() { if (Program.PreviewerDetached) { @@ -255,7 +256,7 @@ namespace Ryujinx.Ava.UI.ViewModels } } - public ControllerInputViewModel() + public InputViewModel() { PlayerIndexes = new ObservableCollection(); Controllers = new ObservableCollection(); @@ -282,12 +283,12 @@ namespace Ryujinx.Ava.UI.ViewModels if (Config is StandardKeyboardInputConfig keyboardInputConfig) { - Configuration = new InputConfiguration(keyboardInputConfig); + ConfigViewModel = new KeyboardInputViewModel(this, new KeyboardInputConfig(keyboardInputConfig)); } if (Config is StandardControllerInputConfig controllerInputConfig) { - Configuration = new InputConfiguration(controllerInputConfig); + ConfigViewModel = new ControllerInputViewModel(this, new GamepadInputConfig(controllerInputConfig)); } } @@ -323,16 +324,6 @@ namespace Ryujinx.Ava.UI.ViewModels } } - public async void ShowMotionConfig() - { - await MotionInputView.Show(this); - } - - public async void ShowRumbleConfig() - { - await RumbleInputView.Show(this); - } - private void LoadInputDriver() { if (_device < 0) @@ -740,7 +731,7 @@ namespace Ryujinx.Ava.UI.ViewModels return; } - if (Configuration == null) + if (ConfigViewModel == null) { return; } @@ -751,35 +742,37 @@ namespace Ryujinx.Ava.UI.ViewModels return; } - - bool validFileName = ProfileName.IndexOfAny(Path.GetInvalidFileNameChars()) == -1; - - if (validFileName) - { - string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json"); - - InputConfig config = null; - - if (IsKeyboard) - { - config = (Configuration as InputConfiguration).GetConfig(); - } - else if (IsController) - { - config = (Configuration as InputConfiguration).GetConfig(); - } - - config.ControllerType = Controllers[_controller].Type; - - string jsonString = JsonHelper.Serialize(config, _serializerContext.InputConfig); - - await File.WriteAllTextAsync(path, jsonString); - - LoadProfiles(); - } else { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileInvalidProfileNameErrorMessage]); + bool validFileName = ProfileName.IndexOfAny(Path.GetInvalidFileNameChars()) == -1; + + if (validFileName) + { + string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json"); + + InputConfig config = null; + + if (IsKeyboard) + { + config = (ConfigViewModel as KeyboardInputViewModel).Config.GetConfig(); + } + else if (IsController) + { + config = (ConfigViewModel as ControllerInputViewModel).Config.GetConfig(); + } + + config.ControllerType = Controllers[_controller].Type; + + string jsonString = JsonHelper.Serialize(config, _serializerContext.InputConfig); + + await File.WriteAllTextAsync(path, jsonString); + + LoadProfiles(); + } + else + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileInvalidProfileNameErrorMessage]); + } } } @@ -830,18 +823,18 @@ namespace Ryujinx.Ava.UI.ViewModels if (device.Type == DeviceType.Keyboard) { - var inputConfig = Configuration as InputConfiguration; + var inputConfig = (ConfigViewModel as KeyboardInputViewModel).Config; inputConfig.Id = device.Id; } else { - var inputConfig = Configuration as InputConfiguration; + var inputConfig = (ConfigViewModel as ControllerInputViewModel).Config; inputConfig.Id = device.Id.Split(" ")[0]; } var config = !IsController - ? (Configuration as InputConfiguration).GetConfig() - : (Configuration as InputConfiguration).GetConfig(); + ? (ConfigViewModel as KeyboardInputViewModel).Config.GetConfig() + : (ConfigViewModel as ControllerInputViewModel).Config.GetConfig(); config.ControllerType = Controllers[_controller].Type; config.PlayerIndex = _playerId; @@ -872,12 +865,13 @@ namespace Ryujinx.Ava.UI.ViewModels public void NotifyChanges() { - OnPropertyChanged(nameof(Configuration)); + OnPropertyChanged(nameof(ConfigViewModel)); OnPropertyChanged(nameof(IsController)); OnPropertyChanged(nameof(ShowSettings)); OnPropertyChanged(nameof(IsKeyboard)); OnPropertyChanged(nameof(IsRight)); OnPropertyChanged(nameof(IsLeft)); + NotifyChangesEvent?.Invoke(); } public void Dispose() diff --git a/src/Ryujinx/UI/ViewModels/Input/KeyboardInputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/KeyboardInputViewModel.cs new file mode 100644 index 0000000000..0b530eb094 --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/Input/KeyboardInputViewModel.cs @@ -0,0 +1,73 @@ +using Avalonia.Svg.Skia; +using Ryujinx.Ava.UI.Models.Input; + +namespace Ryujinx.Ava.UI.ViewModels.Input +{ + public class KeyboardInputViewModel : BaseModel + { + private KeyboardInputConfig _config; + public KeyboardInputConfig Config + { + get => _config; + set + { + _config = value; + OnPropertyChanged(); + } + } + + private bool _isLeft; + public bool IsLeft + { + get => _isLeft; + set + { + _isLeft = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(HasSides)); + } + } + + private bool _isRight; + public bool IsRight + { + get => _isRight; + set + { + _isRight = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(HasSides)); + } + } + + public bool HasSides => IsLeft ^ IsRight; + + private SvgImage _image; + public SvgImage Image + { + get => _image; + set + { + _image = value; + OnPropertyChanged(); + } + } + + public readonly InputViewModel ParentModel; + + public KeyboardInputViewModel(InputViewModel model, KeyboardInputConfig config) + { + ParentModel = model; + model.NotifyChangesEvent += OnParentModelChanged; + OnParentModelChanged(); + Config = config; + } + + public void OnParentModelChanged() + { + IsLeft = ParentModel.IsLeft; + IsRight = ParentModel.IsRight; + Image = ParentModel.Image; + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/MotionInputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/MotionInputViewModel.cs similarity index 97% rename from src/Ryujinx/UI/ViewModels/MotionInputViewModel.cs rename to src/Ryujinx/UI/ViewModels/Input/MotionInputViewModel.cs index 0b12a51f6c..c9ed8f2d46 100644 --- a/src/Ryujinx/UI/ViewModels/MotionInputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/MotionInputViewModel.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.Ava.UI.ViewModels +namespace Ryujinx.Ava.UI.ViewModels.Input { public class MotionInputViewModel : BaseModel { diff --git a/src/Ryujinx/UI/ViewModels/RumbleInputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/RumbleInputViewModel.cs similarity index 92% rename from src/Ryujinx/UI/ViewModels/RumbleInputViewModel.cs rename to src/Ryujinx/UI/ViewModels/Input/RumbleInputViewModel.cs index 49de19937d..8ad33cf4ce 100644 --- a/src/Ryujinx/UI/ViewModels/RumbleInputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/RumbleInputViewModel.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.Ava.UI.ViewModels +namespace Ryujinx.Ava.UI.ViewModels.Input { public class RumbleInputViewModel : BaseModel { diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index fde8f74ae4..6074a5fdb3 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -7,9 +7,9 @@ using Ryujinx.Audio.Backends.SDL2; using Ryujinx.Audio.Backends.SoundIo; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Models.Input; using Ryujinx.Ava.UI.Windows; using Ryujinx.Common.Configuration; -using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Multiplayer; using Ryujinx.Common.GraphicsDriver; using Ryujinx.Common.Logging; @@ -46,7 +46,6 @@ namespace Ryujinx.Ava.UI.ViewModels private bool _isVulkanAvailable = true; private bool _directoryChanged; private readonly List _gpuIds = new(); - private KeyboardHotkeys _keyboardHotkeys; private int _graphicsBackendIndex; private int _scalingFilter; private int _scalingFilterLevel; @@ -237,16 +236,7 @@ namespace Ryujinx.Ava.UI.ViewModels get => new(_networkInterfaces.Keys); } - public KeyboardHotkeys KeyboardHotkeys - { - get => _keyboardHotkeys; - set - { - _keyboardHotkeys = value; - - OnPropertyChanged(); - } - } + public HotkeyConfig KeyboardHotkey { get; set; } public int NetworkInterfaceIndex { @@ -413,7 +403,7 @@ namespace Ryujinx.Ava.UI.ViewModels EnableMouse = config.Hid.EnableMouse; // Keyboard Hotkeys - KeyboardHotkeys = config.Hid.Hotkeys.Value; + KeyboardHotkey = new HotkeyConfig(config.Hid.Hotkeys.Value); // System Region = (int)config.System.Region.Value; @@ -500,7 +490,7 @@ namespace Ryujinx.Ava.UI.ViewModels config.Hid.EnableMouse.Value = EnableMouse; // Keyboard Hotkeys - config.Hid.Hotkeys.Value = KeyboardHotkeys; + config.Hid.Hotkeys.Value = KeyboardHotkey.GetConfig(); // System config.System.Region.Value = (Region)Region; diff --git a/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml b/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml index 99f2b6b694..08bdf90f4c 100644 --- a/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml +++ b/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml @@ -1,13 +1,11 @@ @@ -34,192 +33,10 @@ HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Orientation="Vertical"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + MinHeight="450"> @@ -258,9 +75,9 @@ VerticalAlignment="Center" Text="{locale:Locale ControllerSettingsTriggerZL}" TextAlignment="Center" /> - + @@ -274,9 +91,9 @@ VerticalAlignment="Center" Text="{locale:Locale ControllerSettingsTriggerL}" TextAlignment="Center" /> - + @@ -290,9 +107,9 @@ VerticalAlignment="Center" Text="{locale:Locale ControllerSettingsButtonMinus}" TextAlignment="Center" /> - + @@ -312,100 +129,8 @@ Margin="0,0,0,10" HorizontalAlignment="Center" Text="{locale:Locale ControllerSettingsLStick}" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -416,9 +141,9 @@ VerticalAlignment="Center" Text="{locale:Locale ControllerSettingsStickButton}" TextAlignment="Center" /> - + @@ -433,22 +158,22 @@ VerticalAlignment="Center" Text="{locale:Locale ControllerSettingsStickStick}" TextAlignment="Center" /> - + - + - + - + + Value="{Binding Config.DeadzoneLeft, Mode=TwoWay}" /> + Text="{Binding Config.DeadzoneLeft, StringFormat=\{0:0.00\}}" /> + Value="{Binding Config.RangeLeft, Mode=TwoWay}" /> + Text="{Binding Config.RangeLeft, StringFormat=\{0:0.00\}}" /> @@ -526,9 +251,9 @@ VerticalAlignment="Center" Text="{locale:Locale ControllerSettingsDPadUp}" TextAlignment="Center" /> - + @@ -543,9 +268,9 @@ VerticalAlignment="Center" Text="{locale:Locale ControllerSettingsDPadDown}" TextAlignment="Center" /> - + @@ -560,9 +285,9 @@ VerticalAlignment="Center" Text="{locale:Locale ControllerSettingsDPadLeft}" TextAlignment="Center" /> - + @@ -577,9 +302,9 @@ VerticalAlignment="Center" Text="{locale:Locale ControllerSettingsDPadRight}" TextAlignment="Center" /> - + @@ -592,6 +317,13 @@ Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> + + + Value="{Binding Config.TriggerThreshold, Mode=TwoWay}" /> + Text="{Binding Config.TriggerThreshold, StringFormat=\{0:0.00\}}" /> - + - + IsVisible="{Binding IsLeft}" + Orientation="Horizontal"> - - - - + + + + - + IsVisible="{Binding IsLeft}" + Orientation="Horizontal"> - - - - + + + + - + IsVisible="{Binding IsRight}" + Orientation="Horizontal"> - - - - + + + + - + IsVisible="{Binding IsRight}" + Orientation="Horizontal"> - + + + + - - + HorizontalAlignment="Stretch"> @@ -721,7 +449,7 @@ Margin="10" MinWidth="0" Grid.Column="0" - IsChecked="{ReflectionBinding Configuration.EnableMotion, Mode=TwoWay}"> + IsChecked="{Binding Config.EnableMotion, Mode=TwoWay}"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Views/Input/InputView.axaml.cs b/src/Ryujinx/UI/Views/Input/InputView.axaml.cs new file mode 100644 index 0000000000..356381a8aa --- /dev/null +++ b/src/Ryujinx/UI/Views/Input/InputView.axaml.cs @@ -0,0 +1,61 @@ +using Avalonia.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Ava.UI.ViewModels.Input; + +namespace Ryujinx.Ava.UI.Views.Input +{ + public partial class InputView : UserControl + { + private bool _dialogOpen; + private InputViewModel ViewModel { get; set; } + + public InputView() + { + DataContext = ViewModel = new InputViewModel(this); + + InitializeComponent(); + } + + public void SaveCurrentProfile() + { + ViewModel.Save(); + } + + private async void PlayerIndexBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (ViewModel.IsModified && !_dialogOpen) + { + _dialogOpen = true; + + var result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogControllerSettingsModifiedConfirmMessage], + LocaleManager.Instance[LocaleKeys.DialogControllerSettingsModifiedConfirmSubMessage], + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); + + if (result == UserResult.Yes) + { + ViewModel.Save(); + } + + _dialogOpen = false; + + ViewModel.IsModified = false; + + if (e.AddedItems.Count > 0) + { + var player = (PlayerModel)e.AddedItems[0]; + ViewModel.PlayerId = player.Id; + } + } + } + + public void Dispose() + { + ViewModel.Dispose(); + } + } +} diff --git a/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml b/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml new file mode 100644 index 0000000000..e4566f463d --- /dev/null +++ b/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml @@ -0,0 +1,675 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml.cs b/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml.cs new file mode 100644 index 0000000000..f17c7496ca --- /dev/null +++ b/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml.cs @@ -0,0 +1,208 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.LogicalTree; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.ViewModels.Input; +using Ryujinx.Input; +using Ryujinx.Input.Assigner; +using Key = Ryujinx.Common.Configuration.Hid.Key; + +namespace Ryujinx.Ava.UI.Views.Input +{ + public partial class KeyboardInputView : UserControl + { + private ButtonKeyAssigner _currentAssigner; + + public KeyboardInputView() + { + InitializeComponent(); + + foreach (ILogical visual in SettingButtons.GetLogicalDescendants()) + { + if (visual is ToggleButton button and not CheckBox) + { + button.IsCheckedChanged += Button_IsCheckedChanged; + } + } + } + + protected override void OnPointerReleased(PointerReleasedEventArgs e) + { + base.OnPointerReleased(e); + + if (_currentAssigner != null && _currentAssigner.ToggledButton != null && !_currentAssigner.ToggledButton.IsPointerOver) + { + _currentAssigner.Cancel(); + } + } + + private void Button_IsCheckedChanged(object sender, RoutedEventArgs e) + { + if (sender is ToggleButton button) + { + if ((bool)button.IsChecked) + { + if (_currentAssigner != null && button == _currentAssigner.ToggledButton) + { + return; + } + + if (_currentAssigner == null) + { + _currentAssigner = new ButtonKeyAssigner(button); + + Focus(NavigationMethod.Pointer); + + PointerPressed += MouseClick; + + var viewModel = (DataContext as KeyboardInputViewModel); + + IKeyboard keyboard = (IKeyboard)viewModel.ParentModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations. + IButtonAssigner assigner = CreateButtonAssigner(); + + _currentAssigner.ButtonAssigned += (sender, e) => + { + if (e.ButtonValue.HasValue) + { + var buttonValue = e.ButtonValue.Value; + viewModel.ParentModel.IsModified = true; + + switch (button.Name) + { + case "ButtonZl": + viewModel.Config.ButtonZl = buttonValue.AsHidType(); + break; + case "ButtonL": + viewModel.Config.ButtonL = buttonValue.AsHidType(); + break; + case "ButtonMinus": + viewModel.Config.ButtonMinus = buttonValue.AsHidType(); + break; + case "LeftStickButton": + viewModel.Config.LeftStickButton = buttonValue.AsHidType(); + break; + case "LeftStickUp": + viewModel.Config.LeftStickUp = buttonValue.AsHidType(); + break; + case "LeftStickDown": + viewModel.Config.LeftStickDown = buttonValue.AsHidType(); + break; + case "LeftStickRight": + viewModel.Config.LeftStickRight = buttonValue.AsHidType(); + break; + case "LeftStickLeft": + viewModel.Config.LeftStickLeft = buttonValue.AsHidType(); + break; + case "DpadUp": + viewModel.Config.DpadUp = buttonValue.AsHidType(); + break; + case "DpadDown": + viewModel.Config.DpadDown = buttonValue.AsHidType(); + break; + case "DpadLeft": + viewModel.Config.DpadLeft = buttonValue.AsHidType(); + break; + case "DpadRight": + viewModel.Config.DpadRight = buttonValue.AsHidType(); + break; + case "LeftButtonSr": + viewModel.Config.LeftButtonSr = buttonValue.AsHidType(); + break; + case "LeftButtonSl": + viewModel.Config.LeftButtonSl = buttonValue.AsHidType(); + break; + case "RightButtonSr": + viewModel.Config.RightButtonSr = buttonValue.AsHidType(); + break; + case "RightButtonSl": + viewModel.Config.RightButtonSl = buttonValue.AsHidType(); + break; + case "ButtonZr": + viewModel.Config.ButtonZr = buttonValue.AsHidType(); + break; + case "ButtonR": + viewModel.Config.ButtonR = buttonValue.AsHidType(); + break; + case "ButtonPlus": + viewModel.Config.ButtonPlus = buttonValue.AsHidType(); + break; + case "ButtonA": + viewModel.Config.ButtonA = buttonValue.AsHidType(); + break; + case "ButtonB": + viewModel.Config.ButtonB = buttonValue.AsHidType(); + break; + case "ButtonX": + viewModel.Config.ButtonX = buttonValue.AsHidType(); + break; + case "ButtonY": + viewModel.Config.ButtonY = buttonValue.AsHidType(); + break; + case "RightStickButton": + viewModel.Config.RightStickButton = buttonValue.AsHidType(); + break; + case "RightStickUp": + viewModel.Config.RightStickUp = buttonValue.AsHidType(); + break; + case "RightStickDown": + viewModel.Config.RightStickDown = buttonValue.AsHidType(); + break; + case "RightStickRight": + viewModel.Config.RightStickRight = buttonValue.AsHidType(); + break; + case "RightStickLeft": + viewModel.Config.RightStickLeft = buttonValue.AsHidType(); + break; + } + } + }; + + _currentAssigner.GetInputAndAssign(assigner, keyboard); + } + else + { + if (_currentAssigner != null) + { + _currentAssigner.Cancel(); + _currentAssigner = null; + button.IsChecked = false; + } + } + } + else + { + _currentAssigner?.Cancel(); + _currentAssigner = null; + } + } + } + + private void MouseClick(object sender, PointerPressedEventArgs e) + { + bool shouldUnbind = e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed; + + _currentAssigner?.Cancel(shouldUnbind); + + PointerPressed -= MouseClick; + } + + private IButtonAssigner CreateButtonAssigner() + { + IButtonAssigner assigner; + + assigner = new KeyboardKeyAssigner((IKeyboard)(DataContext as KeyboardInputViewModel).ParentModel.SelectedGamepad); + + return assigner; + } + + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnDetachedFromVisualTree(e); + _currentAssigner?.Cancel(); + _currentAssigner = null; + } + } +} diff --git a/src/Ryujinx/UI/Views/Input/MotionInputView.axaml b/src/Ryujinx/UI/Views/Input/MotionInputView.axaml index a6b587f671..0d018e297a 100644 --- a/src/Ryujinx/UI/Views/Input/MotionInputView.axaml +++ b/src/Ryujinx/UI/Views/Input/MotionInputView.axaml @@ -6,7 +6,7 @@ xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Input" mc:Ignorable="d" x:Class="Ryujinx.Ava.UI.Views.Input.MotionInputView" x:DataType="viewModels:MotionInputViewModel" diff --git a/src/Ryujinx/UI/Views/Input/MotionInputView.axaml.cs b/src/Ryujinx/UI/Views/Input/MotionInputView.axaml.cs index 1b340752b1..2304364b67 100644 --- a/src/Ryujinx/UI/Views/Input/MotionInputView.axaml.cs +++ b/src/Ryujinx/UI/Views/Input/MotionInputView.axaml.cs @@ -1,9 +1,7 @@ using Avalonia.Controls; using FluentAvalonia.UI.Controls; using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Models; -using Ryujinx.Ava.UI.ViewModels; -using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Ava.UI.ViewModels.Input; using System.Threading.Tasks; namespace Ryujinx.Ava.UI.Views.Input @@ -19,7 +17,7 @@ namespace Ryujinx.Ava.UI.Views.Input public MotionInputView(ControllerInputViewModel viewModel) { - var config = viewModel.Configuration as InputConfiguration; + var config = viewModel.Config; _viewModel = new MotionInputViewModel { @@ -51,7 +49,7 @@ namespace Ryujinx.Ava.UI.Views.Input }; contentDialog.PrimaryButtonClick += (sender, args) => { - var config = viewModel.Configuration as InputConfiguration; + var config = viewModel.Config; config.Slot = content._viewModel.Slot; config.Sensitivity = content._viewModel.Sensitivity; config.GyroDeadzone = content._viewModel.GyroDeadzone; diff --git a/src/Ryujinx/UI/Views/Input/RumbleInputView.axaml b/src/Ryujinx/UI/Views/Input/RumbleInputView.axaml index 5b7087a470..1beb1f06e8 100644 --- a/src/Ryujinx/UI/Views/Input/RumbleInputView.axaml +++ b/src/Ryujinx/UI/Views/Input/RumbleInputView.axaml @@ -5,7 +5,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" - xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Input" mc:Ignorable="d" x:Class="Ryujinx.Ava.UI.Views.Input.RumbleInputView" x:DataType="viewModels:RumbleInputViewModel" diff --git a/src/Ryujinx/UI/Views/Input/RumbleInputView.axaml.cs b/src/Ryujinx/UI/Views/Input/RumbleInputView.axaml.cs index 9307f872c3..58a4b416b6 100644 --- a/src/Ryujinx/UI/Views/Input/RumbleInputView.axaml.cs +++ b/src/Ryujinx/UI/Views/Input/RumbleInputView.axaml.cs @@ -1,9 +1,7 @@ using Avalonia.Controls; using FluentAvalonia.UI.Controls; using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Models; -using Ryujinx.Ava.UI.ViewModels; -using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Ava.UI.ViewModels.Input; using System.Threading.Tasks; namespace Ryujinx.Ava.UI.Views.Input @@ -19,7 +17,7 @@ namespace Ryujinx.Ava.UI.Views.Input public RumbleInputView(ControllerInputViewModel viewModel) { - var config = viewModel.Configuration as InputConfiguration; + var config = viewModel.Config; _viewModel = new RumbleInputViewModel { @@ -47,7 +45,7 @@ namespace Ryujinx.Ava.UI.Views.Input contentDialog.PrimaryButtonClick += (sender, args) => { - var config = viewModel.Configuration as InputConfiguration; + var config = viewModel.Config; config.StrongRumble = content._viewModel.StrongRumble; config.WeakRumble = content._viewModel.WeakRumble; }; diff --git a/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml index b4eae01ef9..bffcada055 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml +++ b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml @@ -9,6 +9,7 @@ xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" mc:Ignorable="d" x:DataType="viewModels:SettingsViewModel" + x:CompileBindings="True" Focusable="True"> @@ -16,6 +17,23 @@ + + + + + + - - - - - - + + + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - \ No newline at end of file + diff --git a/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs index b006d703f4..fb0fe2bb12 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs +++ b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs @@ -2,10 +2,13 @@ using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Interactivity; +using Avalonia.LogicalTree; using Ryujinx.Ava.Input; using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Input; using Ryujinx.Input.Assigner; +using Key = Ryujinx.Common.Configuration.Hid.Key; namespace Ryujinx.Ava.UI.Views.Settings { @@ -17,9 +20,28 @@ namespace Ryujinx.Ava.UI.Views.Settings public SettingsHotkeysView() { InitializeComponent(); + + foreach (ILogical visual in SettingButtons.GetLogicalDescendants()) + { + if (visual is ToggleButton button and not CheckBox) + { + button.IsCheckedChanged += Button_IsCheckedChanged; + } + } + _avaloniaKeyboardDriver = new AvaloniaKeyboardDriver(this); } + protected override void OnPointerReleased(PointerReleasedEventArgs e) + { + base.OnPointerReleased(e); + + if (!_currentAssigner?.ToggledButton?.IsPointerOver ?? false) + { + _currentAssigner.Cancel(); + } + } + private void MouseClick(object sender, PointerPressedEventArgs e) { bool shouldUnbind = e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed; @@ -29,53 +51,94 @@ namespace Ryujinx.Ava.UI.Views.Settings PointerPressed -= MouseClick; } - private void Button_Checked(object sender, RoutedEventArgs e) + private void Button_IsCheckedChanged(object sender, RoutedEventArgs e) { if (sender is ToggleButton button) { - if (_currentAssigner != null && button == _currentAssigner.ToggledButton) + if ((bool)button.IsChecked) { - return; - } + if (_currentAssigner != null && button == _currentAssigner.ToggledButton) + { + return; + } - if (_currentAssigner == null && button.IsChecked != null && (bool)button.IsChecked) - { - _currentAssigner = new ButtonKeyAssigner(button); + if (_currentAssigner == null) + { + _currentAssigner = new ButtonKeyAssigner(button); - this.Focus(NavigationMethod.Pointer); + this.Focus(NavigationMethod.Pointer); - PointerPressed += MouseClick; + PointerPressed += MouseClick; - var keyboard = (IKeyboard)_avaloniaKeyboardDriver.GetGamepad(_avaloniaKeyboardDriver.GamepadsIds[0]); - IButtonAssigner assigner = new KeyboardKeyAssigner(keyboard); + var keyboard = (IKeyboard)_avaloniaKeyboardDriver.GetGamepad("0"); + IButtonAssigner assigner = new KeyboardKeyAssigner(keyboard); - _currentAssigner.GetInputAndAssign(assigner); + _currentAssigner.ButtonAssigned += (sender, e) => + { + if (e.ButtonValue.HasValue) + { + var viewModel = (DataContext) as SettingsViewModel; + var buttonValue = e.ButtonValue.Value; + + switch (button.Name) + { + case "ToggleVsync": + viewModel.KeyboardHotkey.ToggleVsync = buttonValue.AsHidType(); + break; + case "Screenshot": + viewModel.KeyboardHotkey.Screenshot = buttonValue.AsHidType(); + break; + case "ShowUI": + viewModel.KeyboardHotkey.ShowUI = buttonValue.AsHidType(); + break; + case "Pause": + viewModel.KeyboardHotkey.Pause = buttonValue.AsHidType(); + break; + case "ToggleMute": + viewModel.KeyboardHotkey.ToggleMute = buttonValue.AsHidType(); + break; + case "ResScaleUp": + viewModel.KeyboardHotkey.ResScaleUp = buttonValue.AsHidType(); + break; + case "ResScaleDown": + viewModel.KeyboardHotkey.ResScaleDown = buttonValue.AsHidType(); + break; + case "VolumeUp": + viewModel.KeyboardHotkey.VolumeUp = buttonValue.AsHidType(); + break; + case "VolumeDown": + viewModel.KeyboardHotkey.VolumeDown = buttonValue.AsHidType(); + break; + } + } + }; + + _currentAssigner.GetInputAndAssign(assigner, keyboard); + } + else + { + if (_currentAssigner != null) + { + _currentAssigner.Cancel(); + _currentAssigner = null; + button.IsChecked = false; + } + } } else { - if (_currentAssigner != null) - { - ToggleButton oldButton = _currentAssigner.ToggledButton; - - _currentAssigner.Cancel(); - _currentAssigner = null; - - button.IsChecked = false; - } + _currentAssigner?.Cancel(); + _currentAssigner = null; } } } - private void Button_Unchecked(object sender, RoutedEventArgs e) - { - _currentAssigner?.Cancel(); - _currentAssigner = null; - } - public void Dispose() { _currentAssigner?.Cancel(); _currentAssigner = null; + + _avaloniaKeyboardDriver.Dispose(); } } } diff --git a/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml index 81f4b68b74..55c2ed6e3c 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml +++ b/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml @@ -27,9 +27,9 @@ - + Name="InputView" /> diff --git a/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml.cs index e75c9f0cc4..55b69af068 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml.cs +++ b/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml.cs @@ -11,7 +11,7 @@ namespace Ryujinx.Ava.UI.Views.Settings public void Dispose() { - ControllerSettings.Dispose(); + InputView.Dispose(); } } } diff --git a/src/Ryujinx/UI/Windows/SettingsWindow.axaml.cs b/src/Ryujinx/UI/Windows/SettingsWindow.axaml.cs index d7bb0b8837..314501c525 100644 --- a/src/Ryujinx/UI/Windows/SettingsWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/SettingsWindow.axaml.cs @@ -37,7 +37,7 @@ namespace Ryujinx.Ava.UI.Windows public void SaveSettings() { - InputPage.ControllerSettings?.SaveCurrentProfile(); + InputPage.InputView?.SaveCurrentProfile(); if (Owner is MainWindow window && ViewModel.DirectoryChanged) { From 3224ddeeb41fbbc831e00246c21c9d769b5a28ac Mon Sep 17 00:00:00 2001 From: Marco Carvalho Date: Thu, 18 Apr 2024 14:52:57 -0300 Subject: [PATCH 55/87] Update "SixLabors.ImageSharp" (#6680) --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index c3af18ceec..ef274125a4 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -42,7 +42,7 @@ - + From 2f93ae9a191e1fb5415227f004e9a48a11b7a180 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Thu, 18 Apr 2024 19:28:16 -0300 Subject: [PATCH 56/87] Fix unmapped address check when reading texture handles (#6679) --- src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs index 3c10c95e02..a1dde673b9 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs @@ -770,7 +770,7 @@ namespace Ryujinx.Graphics.Gpu.Image ? _channel.BufferManager.GetComputeUniformBufferAddress(textureBufferIndex) : _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, textureBufferIndex); - int handle = textureBufferAddress != 0 + int handle = textureBufferAddress != MemoryManager.PteUnmapped ? _channel.MemoryManager.Physical.Read(textureBufferAddress + (uint)textureWordOffset * 4) : 0; @@ -790,7 +790,7 @@ namespace Ryujinx.Graphics.Gpu.Image ? _channel.BufferManager.GetComputeUniformBufferAddress(samplerBufferIndex) : _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, samplerBufferIndex); - samplerHandle = samplerBufferAddress != 0 + samplerHandle = samplerBufferAddress != MemoryManager.PteUnmapped ? _channel.MemoryManager.Physical.Read(samplerBufferAddress + (uint)samplerWordOffset * 4) : 0; } From 22fb8c9d4f01f0356c29454d18004849f89c67dd Mon Sep 17 00:00:00 2001 From: Marco Carvalho Date: Fri, 19 Apr 2024 09:03:52 -0300 Subject: [PATCH 57/87] Update to new standard for volatility operations (#6682) --- src/Ryujinx.HLE/HOS/TamperMachine.cs | 6 +++--- src/Ryujinx.Tests/Memory/PartialUnmaps.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Ryujinx.HLE/HOS/TamperMachine.cs b/src/Ryujinx.HLE/HOS/TamperMachine.cs index f234e540e6..609221535d 100644 --- a/src/Ryujinx.HLE/HOS/TamperMachine.cs +++ b/src/Ryujinx.HLE/HOS/TamperMachine.cs @@ -143,7 +143,7 @@ namespace Ryujinx.HLE.HOS try { - ControllerKeys pressedKeys = (ControllerKeys)Thread.VolatileRead(ref _pressedKeys); + ControllerKeys pressedKeys = (ControllerKeys)Volatile.Read(ref _pressedKeys); program.Process.TamperedCodeMemory = false; program.Execute(pressedKeys); @@ -175,14 +175,14 @@ namespace Ryujinx.HLE.HOS { if (input.PlayerId == PlayerIndex.Player1 || input.PlayerId == PlayerIndex.Handheld) { - Thread.VolatileWrite(ref _pressedKeys, (long)input.Buttons); + Volatile.Write(ref _pressedKeys, (long)input.Buttons); return; } } // Clear the input because player one is not conected. - Thread.VolatileWrite(ref _pressedKeys, 0); + Volatile.Write(ref _pressedKeys, 0); } } } diff --git a/src/Ryujinx.Tests/Memory/PartialUnmaps.cs b/src/Ryujinx.Tests/Memory/PartialUnmaps.cs index 04f7f40e66..ace68e5c24 100644 --- a/src/Ryujinx.Tests/Memory/PartialUnmaps.cs +++ b/src/Ryujinx.Tests/Memory/PartialUnmaps.cs @@ -388,14 +388,14 @@ namespace Ryujinx.Tests.Memory { rwLock.AcquireReaderLock(); - int originalValue = Thread.VolatileRead(ref value); + int originalValue = Volatile.Read(ref value); count++; // Spin a bit. for (int i = 0; i < 100; i++) { - if (Thread.VolatileRead(ref readersAllowed) == 0) + if (Volatile.Read(ref readersAllowed) == 0) { error = true; running = false; @@ -403,7 +403,7 @@ namespace Ryujinx.Tests.Memory } // Should not change while the lock is held. - if (Thread.VolatileRead(ref value) != originalValue) + if (Volatile.Read(ref value) != originalValue) { error = true; running = false; From 99f46e22e2ba6c528dfdc9b3907d5a6acf2b1365 Mon Sep 17 00:00:00 2001 From: Marco Carvalho Date: Fri, 19 Apr 2024 09:21:21 -0300 Subject: [PATCH 58/87] Do not compare Span to 'null' or 'default' (#6683) --- src/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs | 4 ++-- src/Ryujinx.HLE/HOS/Services/Fatal/IService.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs b/src/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs index 61cfbb6ec9..278dbecfa5 100644 --- a/src/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs +++ b/src/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs @@ -302,12 +302,12 @@ namespace Ryujinx.Graphics.Vulkan SubmitInfo sInfo = new() { SType = StructureType.SubmitInfo, - WaitSemaphoreCount = waitSemaphores != null ? (uint)waitSemaphores.Length : 0, + WaitSemaphoreCount = !waitSemaphores.IsEmpty ? (uint)waitSemaphores.Length : 0, PWaitSemaphores = pWaitSemaphores, PWaitDstStageMask = pWaitDstStageMask, CommandBufferCount = 1, PCommandBuffers = &commandBuffer, - SignalSemaphoreCount = signalSemaphores != null ? (uint)signalSemaphores.Length : 0, + SignalSemaphoreCount = !signalSemaphores.IsEmpty ? (uint)signalSemaphores.Length : 0, PSignalSemaphores = pSignalSemaphores, }; diff --git a/src/Ryujinx.HLE/HOS/Services/Fatal/IService.cs b/src/Ryujinx.HLE/HOS/Services/Fatal/IService.cs index 21daf87580..155077745a 100644 --- a/src/Ryujinx.HLE/HOS/Services/Fatal/IService.cs +++ b/src/Ryujinx.HLE/HOS/Services/Fatal/IService.cs @@ -60,7 +60,7 @@ namespace Ryujinx.HLE.HOS.Services.Fatal errorReport.AppendLine($"\tResultCode: {((int)resultCode & 0x1FF) + 2000}-{((int)resultCode >> 9) & 0x3FFF:d4}"); errorReport.AppendLine($"\tFatalPolicy: {fatalPolicy}"); - if (cpuContext != null) + if (!cpuContext.IsEmpty) { errorReport.AppendLine("CPU Context:"); From 9839cd56fbab2412742bf5e8643761aa5bc93e30 Mon Sep 17 00:00:00 2001 From: toofooboo <167498142+toofooboo@users.noreply.github.com> Date: Fri, 19 Apr 2024 20:45:51 +0800 Subject: [PATCH 59/87] chore: remove repetitive words (#6690) Signed-off-by: toofooboo --- src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs | 2 +- src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs b/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs index 099d8f5619..fe1dfc4beb 100644 --- a/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs +++ b/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs @@ -212,7 +212,7 @@ namespace Ryujinx.Audio.Renderer.Server /// /// Check if the audio renderer should fix the GC-ADPCM context not being provided to the DSP. /// - /// True if if the audio renderer should fix it. + /// True if the audio renderer should fix it. public bool IsAdpcmLoopContextBugFixed() { return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision2); diff --git a/src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs b/src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs index 5403c87c07..79b5d743b9 100644 --- a/src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs +++ b/src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs @@ -44,7 +44,7 @@ namespace Ryujinx.Common.Extensions /// /// DO NOT use after calling this method, as it will only /// contain a value if the value couldn't be referenced directly because it spans multiple segments. - /// To discourage use, it is recommended to to call this method like the following: + /// To discourage use, it is recommended to call this method like the following: /// /// ref readonly MyStruct value = ref sequenceReader.GetRefOrRefToCopy{MyStruct}(out _); /// From 7070cf6ae502b5c11551fceb164bc9f158ba980b Mon Sep 17 00:00:00 2001 From: gdkchan Date: Sat, 20 Apr 2024 22:35:20 -0300 Subject: [PATCH 60/87] End render target lifetime on syncpoint increment (#6687) --- src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs | 3 +++ src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs index 5bd8ec728d..cedd824a1a 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs @@ -157,6 +157,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo } else if (operation == SyncpointbOperation.Incr) { + // "Unbind" render targets since a syncpoint increment might indicate future CPU access for the textures. + _parent.TextureManager.RefreshModifiedTextures(); + _context.CreateHostSyncIfNeeded(HostSyncFlags.StrictSyncpoint); _context.Synchronization.IncrementSyncpoint(syncpointId); } diff --git a/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs index b57109c7dd..984a9cff87 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs @@ -4,6 +4,7 @@ using Ryujinx.Graphics.Gpu.Engine.Dma; using Ryujinx.Graphics.Gpu.Engine.InlineToMemory; using Ryujinx.Graphics.Gpu.Engine.Threed; using Ryujinx.Graphics.Gpu.Engine.Twod; +using Ryujinx.Graphics.Gpu.Image; using Ryujinx.Graphics.Gpu.Memory; using System; using System.Runtime.CompilerServices; @@ -28,6 +29,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo /// public MemoryManager MemoryManager => _channel.MemoryManager; + /// + /// Channel texture manager. + /// + public TextureManager TextureManager => _channel.TextureManager; + /// /// 3D Engine. /// From 216026c096d844f8bf09ee0e185dec4111c64095 Mon Sep 17 00:00:00 2001 From: jhorv <38920027+jhorv@users.noreply.github.com> Date: Sun, 21 Apr 2024 06:57:35 -0400 Subject: [PATCH 61/87] Use pooled memory and avoid memory copies (#6691) * perf: use ByteMemoryPool * feat: KPageTableBase/KPageTable new methods to read and write `ReadOnlySequence` * new: add IWritableBlock.Write(ulong, ReadOnlySequence) with default impl * perf: use GetReadOnlySequence() instead of GetSpan() * perf: make `Parcel` IDisposable, use `ByteMemoryPool` for internal allocation, and make Parcel consumers dispose of it * remove comment about copySize * remove unnecessary Clear() --- .../SDL2HardwareDeviceSession.cs | 6 +++- .../SoundIoHardwareDeviceSession.cs | 8 +++-- .../HOS/Kernel/Ipc/KServerSession.cs | 4 +-- .../HOS/Kernel/Memory/KPageTable.cs | 13 +++++++ .../HOS/Kernel/Memory/KPageTableBase.cs | 36 ++++++++++++++----- .../HOS/Services/SurfaceFlinger/IBinder.cs | 4 +-- .../HOS/Services/SurfaceFlinger/Parcel.cs | 32 ++++++++++++----- .../RootService/IApplicationDisplayService.cs | 4 +-- src/Ryujinx.Memory/IWritableBlock.cs | 16 +++++++++ 9 files changed, 98 insertions(+), 25 deletions(-) diff --git a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs index 00188ba58e..62fe5025d6 100644 --- a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs +++ b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs @@ -1,8 +1,10 @@ using Ryujinx.Audio.Backends.Common; using Ryujinx.Audio.Common; using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; using Ryujinx.Memory; using System; +using System.Buffers; using System.Collections.Concurrent; using System.Threading; @@ -87,7 +89,9 @@ namespace Ryujinx.Audio.Backends.SDL2 return; } - byte[] samples = new byte[frameCount * _bytesPerFrame]; + using IMemoryOwner samplesOwner = ByteMemoryPool.Rent(frameCount * _bytesPerFrame); + + Span samples = samplesOwner.Memory.Span; _ringBuffer.Read(samples, 0, samples.Length); diff --git a/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs index f60982e303..4011a12142 100644 --- a/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs +++ b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs @@ -1,8 +1,10 @@ using Ryujinx.Audio.Backends.Common; using Ryujinx.Audio.Backends.SoundIo.Native; using Ryujinx.Audio.Common; +using Ryujinx.Common.Memory; using Ryujinx.Memory; using System; +using System.Buffers; using System.Collections.Concurrent; using System.Runtime.CompilerServices; using System.Threading; @@ -37,7 +39,7 @@ namespace Ryujinx.Audio.Backends.SoundIo _outputStream = _driver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount); _outputStream.WriteCallback += Update; _outputStream.Volume = requestedVolume; - // TODO: Setup other callbacks (errors, ect). + // TODO: Setup other callbacks (errors, etc.) _outputStream.Open(); } @@ -120,7 +122,9 @@ namespace Ryujinx.Audio.Backends.SoundIo int channelCount = areas.Length; - byte[] samples = new byte[frameCount * bytesPerFrame]; + using IMemoryOwner samplesOwner = ByteMemoryPool.Rent(frameCount * bytesPerFrame); + + Span samples = samplesOwner.Memory.Span; _ringBuffer.Read(samples, 0, samples.Length); diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs index 7e41a3f3aa..3b42808557 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs @@ -570,7 +570,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc } else { - serverProcess.CpuMemory.Write(copyDst, clientProcess.CpuMemory.GetSpan(copySrc, (int)copySize)); + serverProcess.CpuMemory.Write(copyDst, clientProcess.CpuMemory.GetReadOnlySequence(copySrc, (int)copySize)); } if (clientResult != Result.Success) @@ -858,7 +858,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc } else { - clientProcess.CpuMemory.Write(copyDst, serverProcess.CpuMemory.GetSpan(copySrc, (int)copySize)); + clientProcess.CpuMemory.Write(copyDst, serverProcess.CpuMemory.GetReadOnlySequence(copySrc, (int)copySize)); } } diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs index d262c159d7..4ffa447dde 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs @@ -2,6 +2,7 @@ using Ryujinx.Horizon.Common; using Ryujinx.Memory; using Ryujinx.Memory.Range; using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; @@ -34,6 +35,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory } } + /// + protected override ReadOnlySequence GetReadOnlySequence(ulong va, int size) + { + return _cpuMemory.GetReadOnlySequence(va, size); + } + /// protected override ReadOnlySpan GetSpan(ulong va, int size) { @@ -247,6 +254,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory _cpuMemory.SignalMemoryTracking(va, size, write); } + /// + protected override void Write(ulong va, ReadOnlySequence data) + { + _cpuMemory.Write(va, data); + } + /// protected override void Write(ulong va, ReadOnlySpan data) { diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs index ae99a434ad..58bbc0dbfb 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs @@ -5,6 +5,7 @@ using Ryujinx.Horizon.Common; using Ryujinx.Memory; using Ryujinx.Memory.Range; using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; @@ -1568,7 +1569,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory while (size > 0) { - ulong copySize = 0x100000; // Copy chunck size. Any value will do, moderate sizes are recommended. + ulong copySize = int.MaxValue; if (copySize > size) { @@ -1577,11 +1578,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory if (toServer) { - currentProcess.CpuMemory.Write(serverAddress, GetSpan(clientAddress, (int)copySize)); + currentProcess.CpuMemory.Write(serverAddress, GetReadOnlySequence(clientAddress, (int)copySize)); } else { - Write(clientAddress, currentProcess.CpuMemory.GetSpan(serverAddress, (int)copySize)); + Write(clientAddress, currentProcess.CpuMemory.GetReadOnlySequence(serverAddress, (int)copySize)); } serverAddress += copySize; @@ -1911,9 +1912,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory Context.Memory.Fill(GetDramAddressFromPa(dstFirstPagePa), unusedSizeBefore, (byte)_ipcFillValue); ulong copySize = addressRounded <= endAddr ? addressRounded - address : size; - var data = srcPageTable.GetSpan(addressTruncated + unusedSizeBefore, (int)copySize); + var data = srcPageTable.GetReadOnlySequence(addressTruncated + unusedSizeBefore, (int)copySize); - Context.Memory.Write(GetDramAddressFromPa(dstFirstPagePa + unusedSizeBefore), data); + ((IWritableBlock)Context.Memory).Write(GetDramAddressFromPa(dstFirstPagePa + unusedSizeBefore), data); firstPageFillAddress += unusedSizeBefore + copySize; @@ -1977,9 +1978,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory if (send) { ulong copySize = endAddr - endAddrTruncated; - var data = srcPageTable.GetSpan(endAddrTruncated, (int)copySize); + var data = srcPageTable.GetReadOnlySequence(endAddrTruncated, (int)copySize); - Context.Memory.Write(GetDramAddressFromPa(dstLastPagePa), data); + ((IWritableBlock)Context.Memory).Write(GetDramAddressFromPa(dstLastPagePa), data); lastPageFillAddr += copySize; @@ -2943,6 +2944,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory /// Page list where the ranges will be added protected abstract void GetPhysicalRegions(ulong va, ulong size, KPageList pageList); + /// + /// Gets a read-only sequence of data from CPU mapped memory. + /// + /// + /// Allows reading non-contiguous memory without first copying it to a newly allocated single contiguous block. + /// + /// Virtual address of the data + /// Size of the data + /// A read-only sequence of the data + /// Throw for unhandled invalid or unmapped memory accesses + protected abstract ReadOnlySequence GetReadOnlySequence(ulong va, int size); + /// /// Gets a read-only span of data from CPU mapped memory. /// @@ -2952,7 +2965,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory /// /// 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 protected abstract ReadOnlySpan GetSpan(ulong va, int size); @@ -3060,6 +3072,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory /// Size of the region protected abstract void SignalMemoryTracking(ulong va, ulong size, bool write); + /// + /// Writes data to CPU mapped memory, with write tracking. + /// + /// Virtual address to write the data into + /// Data to be written + /// Throw for unhandled invalid or unmapped memory accesses + protected abstract void Write(ulong va, ReadOnlySequence data); + /// /// Writes data to CPU mapped memory, with write tracking. /// diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IBinder.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IBinder.cs index 0fb2dfd2ef..54aac48ae3 100644 --- a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IBinder.cs +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IBinder.cs @@ -13,10 +13,10 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger ResultCode OnTransact(uint code, uint flags, ReadOnlySpan inputParcel, Span outputParcel) { - Parcel inputParcelReader = new(inputParcel.ToArray()); + using Parcel inputParcelReader = new(inputParcel); // TODO: support objects? - Parcel outputParcelWriter = new((uint)(outputParcel.Length - Unsafe.SizeOf()), 0); + using Parcel outputParcelWriter = new((uint)(outputParcel.Length - Unsafe.SizeOf()), 0); string inputInterfaceToken = inputParcelReader.ReadInterfaceToken(); diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs index 4ac0525bad..c6cd60d040 100644 --- a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs @@ -1,7 +1,9 @@ using Ryujinx.Common; +using Ryujinx.Common.Memory; using Ryujinx.Common.Utilities; using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types; using System; +using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -9,13 +11,13 @@ using System.Text; namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger { - class Parcel + sealed class Parcel : IDisposable { - private readonly byte[] _rawData; + private readonly IMemoryOwner _rawDataOwner; - private Span Raw => new(_rawData); + private Span Raw => _rawDataOwner.Memory.Span; - private ref ParcelHeader Header => ref MemoryMarshal.Cast(_rawData)[0]; + private ref ParcelHeader Header => ref MemoryMarshal.Cast(Raw)[0]; private Span Payload => Raw.Slice((int)Header.PayloadOffset, (int)Header.PayloadSize); @@ -24,9 +26,11 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger private int _payloadPosition; private int _objectPosition; - public Parcel(byte[] rawData) + private bool _isDisposed; + + public Parcel(ReadOnlySpan data) { - _rawData = rawData; + _rawDataOwner = ByteMemoryPool.RentCopy(data); _payloadPosition = 0; _objectPosition = 0; @@ -36,7 +40,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger { uint headerSize = (uint)Unsafe.SizeOf(); - _rawData = new byte[BitUtils.AlignUp(headerSize + payloadSize + objectsSize, 4)]; + _rawDataOwner = ByteMemoryPool.RentCleared(BitUtils.AlignUp(headerSize + payloadSize + objectsSize, 4)); Header.PayloadSize = payloadSize; Header.ObjectsSize = objectsSize; @@ -132,7 +136,9 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger // TODO: figure out what this value is - WriteInplaceObject(new byte[4] { 0, 0, 0, 0 }); + Span fourBytes = stackalloc byte[4]; + + WriteInplaceObject(fourBytes); } public AndroidStrongPointer ReadStrongPointer() where T : unmanaged, IFlattenable @@ -219,5 +225,15 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger return Raw[..(int)(Header.PayloadSize + Header.ObjectsSize + Unsafe.SizeOf())]; } + + public void Dispose() + { + if (!_isDisposed) + { + _isDisposed = true; + + _rawDataOwner.Dispose(); + } + } } } diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs index b6988f08df..a2b1fb5242 100644 --- a/src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs +++ b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs @@ -250,7 +250,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService context.Device.System.SurfaceFlinger.SetRenderLayer(layerId); - Parcel parcel = new(0x28, 0x4); + using Parcel parcel = new(0x28, 0x4); parcel.WriteObject(producer, "dispdrv\0"); @@ -288,7 +288,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService context.Device.System.SurfaceFlinger.SetRenderLayer(layerId); - Parcel parcel = new(0x28, 0x4); + using Parcel parcel = new(0x28, 0x4); parcel.WriteObject(producer, "dispdrv\0"); diff --git a/src/Ryujinx.Memory/IWritableBlock.cs b/src/Ryujinx.Memory/IWritableBlock.cs index 0858e0c968..78ae2479de 100644 --- a/src/Ryujinx.Memory/IWritableBlock.cs +++ b/src/Ryujinx.Memory/IWritableBlock.cs @@ -1,9 +1,25 @@ using System; +using System.Buffers; namespace Ryujinx.Memory { public interface IWritableBlock { + /// + /// Writes data to CPU mapped memory, with write tracking. + /// + /// Virtual address to write the data into + /// Data to be written + /// Throw for unhandled invalid or unmapped memory accesses + void Write(ulong va, ReadOnlySequence data) + { + foreach (ReadOnlyMemory segment in data) + { + Write(va, segment.Span); + va += (ulong)segment.Length; + } + } + void Write(ulong va, ReadOnlySpan data); void WriteUntracked(ulong va, ReadOnlySpan data) => Write(va, data); From 9b94662b4bb2ebf846e1baf45ba8097fcd7da684 Mon Sep 17 00:00:00 2001 From: jhorv <38920027+jhorv@users.noreply.github.com> Date: Sun, 21 Apr 2024 15:34:04 -0400 Subject: [PATCH 62/87] implement `MemoryManagerHostTracked.GetReadOnlySequence()` (#6695) * implement `MemoryManagerHostTracked.GetReadOnlySequence()`, fixes crashes on game starts on MacOS * whitespace fixes * whitespace fixes * add missing call to `SignalMemoryTracking()` * adjust call to `SignalMemoryTracking()`` * don't use GetPhysicalAddressMemory() * add newline for consistency --- .../Jit/MemoryManagerHostTracked.cs | 64 +++++++++++++++++++ src/Ryujinx.Memory/NativeMemoryManager.cs | 5 ++ 2 files changed, 69 insertions(+) diff --git a/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs b/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs index 0acb57be42..663d0aeb15 100644 --- a/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs +++ b/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs @@ -85,6 +85,70 @@ namespace Ryujinx.Cpu.Jit _addressSpace = new(Tracking, backingMemory, _nativePageTable, useProtectionMirrors); } + public override ReadOnlySequence GetReadOnlySequence(ulong va, int size, bool tracked = false) + { + if (size == 0) + { + return ReadOnlySequence.Empty; + } + + try + { + if (tracked) + { + SignalMemoryTracking(va, (ulong)size, false); + } + else + { + AssertValidAddressAndSize(va, (ulong)size); + } + + ulong endVa = va + (ulong)size; + int offset = 0; + + BytesReadOnlySequenceSegment first = null, last = null; + + while (va < endVa) + { + (MemoryBlock memory, ulong rangeOffset, ulong copySize) = GetMemoryOffsetAndSize(va, (ulong)(size - offset)); + + Memory physicalMemory = memory.GetMemory(rangeOffset, (int)copySize); + + if (first is null) + { + first = last = new BytesReadOnlySequenceSegment(physicalMemory); + } + else + { + if (last.IsContiguousWith(physicalMemory, out nuint contiguousStart, out int contiguousSize)) + { + Memory contiguousPhysicalMemory = new NativeMemoryManager(contiguousStart, contiguousSize).Memory; + + last.Replace(contiguousPhysicalMemory); + } + else + { + last = last.Append(physicalMemory); + } + } + + va += copySize; + offset += (int)copySize; + } + + return new ReadOnlySequence(first, 0, last, (int)(size - last.RunningIndex)); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + + return ReadOnlySequence.Empty; + } + } + /// public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags) { diff --git a/src/Ryujinx.Memory/NativeMemoryManager.cs b/src/Ryujinx.Memory/NativeMemoryManager.cs index 9ca6329382..cb8d5c243d 100644 --- a/src/Ryujinx.Memory/NativeMemoryManager.cs +++ b/src/Ryujinx.Memory/NativeMemoryManager.cs @@ -8,6 +8,11 @@ namespace Ryujinx.Memory private readonly T* _pointer; private readonly int _length; + public NativeMemoryManager(nuint pointer, int length) + : this((T*)pointer, length) + { + } + public NativeMemoryManager(T* pointer, int length) { _pointer = pointer; From c6f8bfed904e30f7c5d890a2f0ef531eb9e298e5 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Mon, 22 Apr 2024 15:05:55 -0300 Subject: [PATCH 63/87] Add support for bindless textures from shader input (vertex buffer) on Vulkan (#6577) * Add support for bindless textures from shader input (vertex buffer) * Shader cache version bump * Format whitespace * Remove cache entries on pool removal, disable for OpenGL * PR feedback --- src/Ryujinx.Graphics.GAL/Capabilities.cs | 3 + .../Engine/Compute/ComputeClass.cs | 6 +- .../Engine/Threed/StateUpdater.cs | 13 +- src/Ryujinx.Graphics.Gpu/Image/PoolCache.cs | 7 +- .../Image/TextureBindingInfo.cs | 16 +- .../Image/TextureBindingsArrayCache.cs | 630 ++++++++++++++---- .../Image/TextureBindingsManager.cs | 16 +- .../Image/TextureManager.cs | 8 +- .../Shader/CachedShaderBindings.cs | 5 +- .../Shader/DiskCache/DiskCacheGpuAccessor.cs | 35 + .../Shader/DiskCache/DiskCacheHostStorage.cs | 2 +- .../Shader/GpuAccessor.cs | 18 + .../Shader/GpuAccessorBase.cs | 2 + .../Shader/GpuAccessorState.cs | 8 + .../Shader/GpuChannelPoolState.cs | 7 +- .../Shader/ShaderCache.cs | 8 +- .../Shader/ShaderInfoBuilder.cs | 38 +- .../Shader/ShaderSpecializationState.cs | 92 ++- src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs | 1 + .../CodeGen/Glsl/Declarations.cs | 3 +- .../Glsl/Instructions/InstGenMemory.cs | 19 +- .../CodeGen/Spirv/Declarations.cs | 52 +- .../CodeGen/Spirv/Instructions.cs | 86 +-- src/Ryujinx.Graphics.Shader/IGpuAccessor.cs | 35 +- .../IntermediateRepresentation/Operation.cs | 5 + .../TextureOperation.cs | 9 + src/Ryujinx.Graphics.Shader/SamplerType.cs | 26 + .../StructuredIr/AstTextureOperation.cs | 5 + .../StructuredIr/StructuredProgram.cs | 2 +- .../StructuredIr/TextureDefinition.cs | 18 +- .../TextureDescriptor.cs | 4 + src/Ryujinx.Graphics.Shader/TextureHandle.cs | 1 + .../Optimizations/BindlessElimination.cs | 86 ++- .../Optimizations/BindlessToArray.cs | 2 +- .../Translation/ResourceManager.cs | 32 +- .../Translation/TranslatorContext.cs | 6 +- .../DescriptorSetTemplate.cs | 85 +-- src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs | 1 + src/Ryujinx.ShaderTools/Program.cs | 10 + 39 files changed, 1091 insertions(+), 311 deletions(-) diff --git a/src/Ryujinx.Graphics.GAL/Capabilities.cs b/src/Ryujinx.Graphics.GAL/Capabilities.cs index dc927eaba1..70736fbd61 100644 --- a/src/Ryujinx.Graphics.GAL/Capabilities.cs +++ b/src/Ryujinx.Graphics.GAL/Capabilities.cs @@ -36,6 +36,7 @@ namespace Ryujinx.Graphics.GAL public readonly bool SupportsMismatchingViewFormat; public readonly bool SupportsCubemapView; public readonly bool SupportsNonConstantTextureOffset; + public readonly bool SupportsSeparateSampler; public readonly bool SupportsShaderBallot; public readonly bool SupportsShaderBarrierDivergence; public readonly bool SupportsShaderFloat64; @@ -92,6 +93,7 @@ namespace Ryujinx.Graphics.GAL bool supportsMismatchingViewFormat, bool supportsCubemapView, bool supportsNonConstantTextureOffset, + bool supportsSeparateSampler, bool supportsShaderBallot, bool supportsShaderBarrierDivergence, bool supportsShaderFloat64, @@ -144,6 +146,7 @@ namespace Ryujinx.Graphics.GAL SupportsMismatchingViewFormat = supportsMismatchingViewFormat; SupportsCubemapView = supportsCubemapView; SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset; + SupportsSeparateSampler = supportsSeparateSampler; SupportsShaderBallot = supportsShaderBallot; SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence; SupportsShaderFloat64 = supportsShaderFloat64; diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs b/src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs index ccdbe4748b..cd81447240 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs @@ -126,6 +126,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute ulong samplerPoolGpuVa = ((ulong)_state.State.SetTexSamplerPoolAOffsetUpper << 32) | _state.State.SetTexSamplerPoolB; ulong texturePoolGpuVa = ((ulong)_state.State.SetTexHeaderPoolAOffsetUpper << 32) | _state.State.SetTexHeaderPoolB; + int samplerPoolMaximumId = _state.State.SetTexSamplerPoolCMaximumIndex; + GpuChannelPoolState poolState = new( texturePoolGpuVa, _state.State.SetTexHeaderPoolCMaximumIndex, @@ -139,7 +141,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute sharedMemorySize, _channel.BufferManager.HasUnalignedStorageBuffers); - CachedShaderProgram cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, poolState, computeState, shaderGpuVa); + CachedShaderProgram cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, samplerPoolMaximumId, poolState, computeState, shaderGpuVa); _context.Renderer.Pipeline.SetProgram(cs.HostProgram); @@ -184,7 +186,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute sharedMemorySize, _channel.BufferManager.HasUnalignedStorageBuffers); - cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, poolState, computeState, shaderGpuVa); + cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, samplerPoolMaximumId, poolState, computeState, shaderGpuVa); _context.Renderer.Pipeline.SetProgram(cs.HostProgram); } diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs index b3eb62185a..1dc77b52df 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs @@ -1429,7 +1429,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed addressesSpan[index] = baseAddress + shader.Offset; } - CachedShaderProgram gs = shaderCache.GetGraphicsShader(ref _state.State, ref _pipeline, _channel, ref _currentSpecState.GetPoolState(), ref _currentSpecState.GetGraphicsState(), addresses); + int samplerPoolMaximumId = _state.State.SamplerIndex == SamplerIndex.ViaHeaderIndex + ? _state.State.TexturePoolState.MaximumId + : _state.State.SamplerPoolState.MaximumId; + + CachedShaderProgram gs = shaderCache.GetGraphicsShader( + ref _state.State, + ref _pipeline, + _channel, + samplerPoolMaximumId, + ref _currentSpecState.GetPoolState(), + ref _currentSpecState.GetGraphicsState(), + addresses); // Consume the modified flag for spec state so that it isn't checked again. _currentSpecState.SetShader(gs); diff --git a/src/Ryujinx.Graphics.Gpu/Image/PoolCache.cs b/src/Ryujinx.Graphics.Gpu/Image/PoolCache.cs index d9881f897e..50872ab632 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/PoolCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/PoolCache.cs @@ -62,8 +62,9 @@ namespace Ryujinx.Graphics.Gpu.Image /// GPU channel that the texture pool cache belongs to /// Start address of the texture pool /// Maximum ID of the texture pool + /// Cache of texture array bindings /// The found or newly created texture pool - public T FindOrCreate(GpuChannel channel, ulong address, int maximumId) + public T FindOrCreate(GpuChannel channel, ulong address, int maximumId, TextureBindingsArrayCache bindingsArrayCache) { // Remove old entries from the cache, if possible. while (_pools.Count > MaxCapacity && (_currentTimestamp - _pools.First.Value.CacheTimestamp) >= MinDeltaForRemoval) @@ -73,6 +74,7 @@ namespace Ryujinx.Graphics.Gpu.Image _pools.RemoveFirst(); oldestPool.Dispose(); oldestPool.CacheNode = null; + bindingsArrayCache.RemoveAllWithPool(oldestPool); } T pool; @@ -87,8 +89,7 @@ namespace Ryujinx.Graphics.Gpu.Image if (pool.CacheNode != _pools.Last) { _pools.Remove(pool.CacheNode); - - pool.CacheNode = _pools.AddLast(pool); + _pools.AddLast(pool.CacheNode); } pool.CacheTimestamp = _currentTimestamp; diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs index 12a457dbcb..ba895c60a8 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs @@ -44,6 +44,11 @@ namespace Ryujinx.Graphics.Gpu.Image /// public TextureUsageFlags Flags { get; } + /// + /// Indicates that the binding is for a sampler. + /// + public bool IsSamplerOnly { get; } + /// /// Constructs the texture binding information structure. /// @@ -74,8 +79,17 @@ namespace Ryujinx.Graphics.Gpu.Image /// Constant buffer slot where the texture handle is located /// The shader texture handle (read index into the texture constant buffer) /// The texture's usage flags, indicating how it is used in the shader - public TextureBindingInfo(Target target, int binding, int arrayLength, int cbufSlot, int handle, TextureUsageFlags flags) : this(target, (Format)0, binding, arrayLength, cbufSlot, handle, flags) + /// Indicates that the binding is for a sampler + public TextureBindingInfo( + Target target, + int binding, + int arrayLength, + int cbufSlot, + int handle, + TextureUsageFlags flags, + bool isSamplerOnly) : this(target, 0, binding, arrayLength, cbufSlot, handle, flags) { + IsSamplerOnly = isSamplerOnly; } } } diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs index 4645317c4f..7e486e0a84 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs @@ -21,12 +21,98 @@ namespace Ryujinx.Graphics.Gpu.Image private readonly GpuContext _context; private readonly GpuChannel _channel; - private readonly bool _isCompute; /// /// Array cache entry key. /// - private readonly struct CacheEntryKey : IEquatable + private readonly struct CacheEntryFromPoolKey : IEquatable + { + /// + /// Whether the entry is for an image. + /// + public readonly bool IsImage; + + /// + /// Whether the entry is for a sampler. + /// + public readonly bool IsSampler; + + /// + /// Texture or image target type. + /// + public readonly Target Target; + + /// + /// Number of entries of the array. + /// + public readonly int ArrayLength; + + private readonly TexturePool _texturePool; + private readonly SamplerPool _samplerPool; + + /// + /// Creates a new array cache entry. + /// + /// Whether the entry is for an image + /// Binding information for the array + /// Texture pool where the array textures are located + /// Sampler pool where the array samplers are located + public CacheEntryFromPoolKey(bool isImage, TextureBindingInfo bindingInfo, TexturePool texturePool, SamplerPool samplerPool) + { + IsImage = isImage; + IsSampler = bindingInfo.IsSamplerOnly; + Target = bindingInfo.Target; + ArrayLength = bindingInfo.ArrayLength; + + _texturePool = texturePool; + _samplerPool = samplerPool; + } + + /// + /// Checks if the pool matches the cached pool. + /// + /// Texture or sampler pool instance + /// True if the pool matches, false otherwise + public bool MatchesPool(IPool pool) + { + return _texturePool == pool || _samplerPool == pool; + } + + /// + /// Checks if the texture and sampler pools matches the cached pools. + /// + /// Texture pool instance + /// Sampler pool instance + /// True if the pools match, false otherwise + private bool MatchesPools(TexturePool texturePool, SamplerPool samplerPool) + { + return _texturePool == texturePool && _samplerPool == samplerPool; + } + + public bool Equals(CacheEntryFromPoolKey other) + { + return IsImage == other.IsImage && + IsSampler == other.IsSampler && + Target == other.Target && + ArrayLength == other.ArrayLength && + MatchesPools(other._texturePool, other._samplerPool); + } + + public override bool Equals(object obj) + { + return obj is CacheEntryFromBufferKey other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(_texturePool, _samplerPool, IsSampler); + } + } + + /// + /// Array cache entry key. + /// + private readonly struct CacheEntryFromBufferKey : IEquatable { /// /// Whether the entry is for an image. @@ -61,7 +147,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// Texture pool where the array textures are located /// Sampler pool where the array samplers are located /// Constant buffer bounds with the texture handles - public CacheEntryKey( + public CacheEntryFromBufferKey( bool isImage, TextureBindingInfo bindingInfo, TexturePool texturePool, @@ -100,7 +186,7 @@ namespace Ryujinx.Graphics.Gpu.Image return _textureBufferBounds.Equals(textureBufferBounds); } - public bool Equals(CacheEntryKey other) + public bool Equals(CacheEntryFromBufferKey other) { return IsImage == other.IsImage && Target == other.Target && @@ -112,7 +198,7 @@ namespace Ryujinx.Graphics.Gpu.Image public override bool Equals(object obj) { - return obj is CacheEntryKey other && Equals(other); + return obj is CacheEntryFromBufferKey other && Equals(other); } public override int GetHashCode() @@ -122,40 +208,15 @@ namespace Ryujinx.Graphics.Gpu.Image } /// - /// Array cache entry. + /// Array cache entry from pool. /// private class CacheEntry { - /// - /// Key for this entry on the cache. - /// - public readonly CacheEntryKey Key; - - /// - /// Linked list node used on the texture bindings array cache. - /// - public LinkedListNode CacheNode; - - /// - /// Timestamp set on the last use of the array by the cache. - /// - public int CacheTimestamp; - /// /// All cached textures, along with their invalidated sequence number as value. /// public readonly Dictionary Textures; - /// - /// All pool texture IDs along with their textures. - /// - public readonly Dictionary TextureIds; - - /// - /// All pool sampler IDs along with their samplers. - /// - public readonly Dictionary SamplerIds; - /// /// Backend texture array if the entry is for a texture, otherwise null. /// @@ -166,44 +227,39 @@ namespace Ryujinx.Graphics.Gpu.Image /// public readonly IImageArray ImageArray; - private readonly TexturePool _texturePool; - private readonly SamplerPool _samplerPool; + /// + /// Texture pool where the array textures are located. + /// + protected readonly TexturePool TexturePool; + + /// + /// Sampler pool where the array samplers are located. + /// + protected readonly SamplerPool SamplerPool; private int _texturePoolSequence; private int _samplerPoolSequence; - private int[] _cachedTextureBuffer; - private int[] _cachedSamplerBuffer; - - private int _lastSequenceNumber; - /// /// Creates a new array cache entry. /// - /// Key for this entry on the cache /// Texture pool where the array textures are located /// Sampler pool where the array samplers are located - private CacheEntry(ref CacheEntryKey key, TexturePool texturePool, SamplerPool samplerPool) + private CacheEntry(TexturePool texturePool, SamplerPool samplerPool) { - Key = key; Textures = new Dictionary(); - TextureIds = new Dictionary(); - SamplerIds = new Dictionary(); - _texturePool = texturePool; - _samplerPool = samplerPool; - - _lastSequenceNumber = -1; + TexturePool = texturePool; + SamplerPool = samplerPool; } /// /// Creates a new array cache entry. /// - /// Key for this entry on the cache /// Backend texture array /// Texture pool where the array textures are located /// Sampler pool where the array samplers are located - public CacheEntry(ref CacheEntryKey key, ITextureArray array, TexturePool texturePool, SamplerPool samplerPool) : this(ref key, texturePool, samplerPool) + public CacheEntry(ITextureArray array, TexturePool texturePool, SamplerPool samplerPool) : this(texturePool, samplerPool) { TextureArray = array; } @@ -211,11 +267,10 @@ namespace Ryujinx.Graphics.Gpu.Image /// /// Creates a new array cache entry. /// - /// Key for this entry on the cache /// Backend image array /// Texture pool where the array textures are located /// Sampler pool where the array samplers are located - public CacheEntry(ref CacheEntryKey key, IImageArray array, TexturePool texturePool, SamplerPool samplerPool) : this(ref key, texturePool, samplerPool) + public CacheEntry(IImageArray array, TexturePool texturePool, SamplerPool samplerPool) : this(texturePool, samplerPool) { ImageArray = array; } @@ -248,23 +303,9 @@ namespace Ryujinx.Graphics.Gpu.Image /// /// Clears all cached texture instances. /// - public void Reset() + public virtual void Reset() { Textures.Clear(); - TextureIds.Clear(); - SamplerIds.Clear(); - } - - /// - /// Updates the cached constant buffer data. - /// - /// Constant buffer data with the texture handles (and sampler handles, if they are combined) - /// Constant buffer data with the sampler handles - /// Whether and comes from different buffers - public void UpdateData(ReadOnlySpan cachedTextureBuffer, ReadOnlySpan cachedSamplerBuffer, bool separateSamplerBuffer) - { - _cachedTextureBuffer = cachedTextureBuffer.ToArray(); - _cachedSamplerBuffer = separateSamplerBuffer ? cachedSamplerBuffer.ToArray() : _cachedTextureBuffer; } /// @@ -287,39 +328,105 @@ namespace Ryujinx.Graphics.Gpu.Image /// /// Checks if the cached texture or sampler pool has been modified since the last call to this method. /// - /// True if any used entries of the pools might have been modified, false otherwise - public bool PoolsModified() + /// True if any used entries of the pool might have been modified, false otherwise + public bool TexturePoolModified() { - bool texturePoolModified = _texturePool.WasModified(ref _texturePoolSequence); - bool samplerPoolModified = _samplerPool.WasModified(ref _samplerPoolSequence); + return TexturePool.WasModified(ref _texturePoolSequence); + } - // If both pools were not modified since the last check, we have nothing else to check. - if (!texturePoolModified && !samplerPoolModified) - { - return false; - } + /// + /// Checks if the cached texture or sampler pool has been modified since the last call to this method. + /// + /// True if any used entries of the pool might have been modified, false otherwise + public bool SamplerPoolModified() + { + return SamplerPool.WasModified(ref _samplerPoolSequence); + } + } - // If the pools were modified, let's check if any of the entries we care about changed. + /// + /// Array cache entry from constant buffer. + /// + private class CacheEntryFromBuffer : CacheEntry + { + /// + /// Key for this entry on the cache. + /// + public readonly CacheEntryFromBufferKey Key; - // Check if any of our cached textures changed on the pool. - foreach ((int textureId, Texture texture) in TextureIds) - { - if (_texturePool.GetCachedItem(textureId) != texture) - { - return true; - } - } + /// + /// Linked list node used on the texture bindings array cache. + /// + public LinkedListNode CacheNode; - // Check if any of our cached samplers changed on the pool. - foreach ((int samplerId, Sampler sampler) in SamplerIds) - { - if (_samplerPool.GetCachedItem(samplerId) != sampler) - { - return true; - } - } + /// + /// Timestamp set on the last use of the array by the cache. + /// + public int CacheTimestamp; - return false; + /// + /// All pool texture IDs along with their textures. + /// + public readonly Dictionary TextureIds; + + /// + /// All pool sampler IDs along with their samplers. + /// + public readonly Dictionary SamplerIds; + + private int[] _cachedTextureBuffer; + private int[] _cachedSamplerBuffer; + + private int _lastSequenceNumber; + + /// + /// Creates a new array cache entry. + /// + /// Key for this entry on the cache + /// Backend texture array + /// Texture pool where the array textures are located + /// Sampler pool where the array samplers are located + public CacheEntryFromBuffer(ref CacheEntryFromBufferKey key, ITextureArray array, TexturePool texturePool, SamplerPool samplerPool) : base(array, texturePool, samplerPool) + { + Key = key; + _lastSequenceNumber = -1; + TextureIds = new Dictionary(); + SamplerIds = new Dictionary(); + } + + /// + /// Creates a new array cache entry. + /// + /// Key for this entry on the cache + /// Backend image array + /// Texture pool where the array textures are located + /// Sampler pool where the array samplers are located + public CacheEntryFromBuffer(ref CacheEntryFromBufferKey key, IImageArray array, TexturePool texturePool, SamplerPool samplerPool) : base(array, texturePool, samplerPool) + { + Key = key; + _lastSequenceNumber = -1; + TextureIds = new Dictionary(); + SamplerIds = new Dictionary(); + } + + /// + public override void Reset() + { + base.Reset(); + TextureIds.Clear(); + SamplerIds.Clear(); + } + + /// + /// Updates the cached constant buffer data. + /// + /// Constant buffer data with the texture handles (and sampler handles, if they are combined) + /// Constant buffer data with the sampler handles + /// Whether and comes from different buffers + public void UpdateData(ReadOnlySpan cachedTextureBuffer, ReadOnlySpan cachedSamplerBuffer, bool separateSamplerBuffer) + { + _cachedTextureBuffer = cachedTextureBuffer.ToArray(); + _cachedSamplerBuffer = separateSamplerBuffer ? cachedSamplerBuffer.ToArray() : _cachedTextureBuffer; } /// @@ -380,10 +487,51 @@ namespace Ryujinx.Graphics.Gpu.Image return true; } + + /// + /// Checks if the cached texture or sampler pool has been modified since the last call to this method. + /// + /// True if any used entries of the pools might have been modified, false otherwise + public bool PoolsModified() + { + bool texturePoolModified = TexturePoolModified(); + bool samplerPoolModified = SamplerPoolModified(); + + // If both pools were not modified since the last check, we have nothing else to check. + if (!texturePoolModified && !samplerPoolModified) + { + return false; + } + + // If the pools were modified, let's check if any of the entries we care about changed. + + // Check if any of our cached textures changed on the pool. + foreach ((int textureId, (Texture texture, TextureDescriptor descriptor)) in TextureIds) + { + if (TexturePool.GetCachedItem(textureId) != texture || + (texture == null && TexturePool.IsValidId(textureId) && !TexturePool.GetDescriptorRef(textureId).Equals(descriptor))) + { + return true; + } + } + + // Check if any of our cached samplers changed on the pool. + foreach ((int samplerId, (Sampler sampler, SamplerDescriptor descriptor)) in SamplerIds) + { + if (SamplerPool.GetCachedItem(samplerId) != sampler || + (sampler == null && SamplerPool.IsValidId(samplerId) && !SamplerPool.GetDescriptorRef(samplerId).Equals(descriptor))) + { + return true; + } + } + + return false; + } } - private readonly Dictionary _cache; - private readonly LinkedList _lruCache; + private readonly Dictionary _cacheFromBuffer; + private readonly Dictionary _cacheFromPool; + private readonly LinkedList _lruCache; private int _currentTimestamp; @@ -392,14 +540,13 @@ namespace Ryujinx.Graphics.Gpu.Image /// /// GPU context /// GPU channel - /// Whether the bindings will be used for compute or graphics pipelines - public TextureBindingsArrayCache(GpuContext context, GpuChannel channel, bool isCompute) + public TextureBindingsArrayCache(GpuContext context, GpuChannel channel) { _context = context; _channel = channel; - _isCompute = isCompute; - _cache = new Dictionary(); - _lruCache = new LinkedList(); + _cacheFromBuffer = new Dictionary(); + _cacheFromPool = new Dictionary(); + _lruCache = new LinkedList(); } /// @@ -457,15 +604,180 @@ namespace Ryujinx.Graphics.Gpu.Image bool isImage, SamplerIndex samplerIndex, TextureBindingInfo bindingInfo) + { + if (IsDirectHandleType(bindingInfo.Handle)) + { + UpdateFromPool(texturePool, samplerPool, stage, isImage, bindingInfo); + } + else + { + UpdateFromBuffer(texturePool, samplerPool, stage, stageIndex, textureBufferIndex, isImage, samplerIndex, bindingInfo); + } + } + + /// + /// Updates a texture or image array bindings and textures from a texture or sampler pool. + /// + /// Texture pool + /// Sampler pool + /// Shader stage where the array is used + /// Whether the array is a image or texture array + /// Array binding information + private void UpdateFromPool(TexturePool texturePool, SamplerPool samplerPool, ShaderStage stage, bool isImage, TextureBindingInfo bindingInfo) + { + CacheEntry entry = GetOrAddEntry(texturePool, samplerPool, bindingInfo, isImage, out bool isNewEntry); + + bool isSampler = bindingInfo.IsSamplerOnly; + bool poolModified = isSampler ? entry.SamplerPoolModified() : entry.TexturePoolModified(); + bool isStore = bindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore); + bool resScaleUnsupported = bindingInfo.Flags.HasFlag(TextureUsageFlags.ResScaleUnsupported); + + if (!poolModified && !isNewEntry && entry.ValidateTextures()) + { + entry.SynchronizeMemory(isStore, resScaleUnsupported); + + if (isImage) + { + _context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray); + } + else + { + _context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray); + } + + return; + } + + if (!isNewEntry) + { + entry.Reset(); + } + + int length = (isSampler ? samplerPool.MaximumId : texturePool.MaximumId) + 1; + length = Math.Min(length, bindingInfo.ArrayLength); + + Format[] formats = isImage ? new Format[bindingInfo.ArrayLength] : null; + ISampler[] samplers = isImage ? null : new ISampler[bindingInfo.ArrayLength]; + ITexture[] textures = new ITexture[bindingInfo.ArrayLength]; + + for (int index = 0; index < length; index++) + { + Texture texture = null; + Sampler sampler = null; + + if (isSampler) + { + sampler = samplerPool?.Get(index); + } + else + { + ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(index, out texture); + + if (texture != null) + { + entry.Textures[texture] = texture.InvalidatedSequence; + + if (isStore) + { + texture.SignalModified(); + } + + if (resScaleUnsupported && texture.ScaleMode != TextureScaleMode.Blacklisted) + { + // Scaling textures used on arrays is currently not supported. + + texture.BlacklistScale(); + } + } + } + + ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target); + ISampler hostSampler = sampler?.GetHostSampler(texture); + + Format format = bindingInfo.Format; + + if (hostTexture != null && texture.Target == Target.TextureBuffer) + { + // Ensure that the buffer texture is using the correct buffer as storage. + // Buffers are frequently re-created to accommodate larger data, so we need to re-bind + // to ensure we're not using a old buffer that was already deleted. + if (isImage) + { + if (format == 0 && texture != null) + { + format = texture.Format; + } + + _channel.BufferManager.SetBufferTextureStorage(entry.ImageArray, hostTexture, texture.Range, bindingInfo, index, format); + } + else + { + _channel.BufferManager.SetBufferTextureStorage(entry.TextureArray, hostTexture, texture.Range, bindingInfo, index, format); + } + } + else if (isImage) + { + if (format == 0 && texture != null) + { + format = texture.Format; + } + + formats[index] = format; + textures[index] = hostTexture; + } + else + { + samplers[index] = hostSampler; + textures[index] = hostTexture; + } + } + + if (isImage) + { + entry.ImageArray.SetFormats(0, formats); + entry.ImageArray.SetImages(0, textures); + + _context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray); + } + else + { + entry.TextureArray.SetSamplers(0, samplers); + entry.TextureArray.SetTextures(0, textures); + + _context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray); + } + } + + /// + /// Updates a texture or image array bindings and textures from constant buffer handles. + /// + /// Texture pool + /// Sampler pool + /// Shader stage where the array is used + /// Shader stage index where the array is used + /// Texture constant buffer index + /// Whether the array is a image or texture array + /// Sampler handles source + /// Array binding information + private void UpdateFromBuffer( + TexturePool texturePool, + SamplerPool samplerPool, + ShaderStage stage, + int stageIndex, + int textureBufferIndex, + bool isImage, + SamplerIndex samplerIndex, + TextureBindingInfo bindingInfo) { (textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, textureBufferIndex); bool separateSamplerBuffer = textureBufferIndex != samplerBufferIndex; + bool isCompute = stage == ShaderStage.Compute; - ref BufferBounds textureBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, textureBufferIndex); - ref BufferBounds samplerBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, samplerBufferIndex); + ref BufferBounds textureBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, textureBufferIndex); + ref BufferBounds samplerBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, samplerBufferIndex); - CacheEntry entry = GetOrAddEntry( + CacheEntryFromBuffer entry = GetOrAddEntry( texturePool, samplerPool, bindingInfo, @@ -589,8 +901,8 @@ namespace Ryujinx.Graphics.Gpu.Image Sampler sampler = samplerPool?.Get(samplerId); - entry.TextureIds[textureId] = texture; - entry.SamplerIds[samplerId] = sampler; + entry.TextureIds[textureId] = (texture, descriptor); + entry.SamplerIds[samplerId] = (sampler, samplerPool?.GetDescriptorRef(samplerId) ?? default); ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target); ISampler hostSampler = sampler?.GetHostSampler(texture); @@ -650,13 +962,12 @@ namespace Ryujinx.Graphics.Gpu.Image } /// - /// Gets a cached texture entry, or creates a new one if not found. + /// Gets a cached texture entry from pool, or creates a new one if not found. /// /// Texture pool /// Sampler pool /// Array binding information /// Whether the array is a image or texture array - /// Constant buffer bounds with the texture handles /// Whether a new entry was created, or an existing one was returned /// Cache entry private CacheEntry GetOrAddEntry( @@ -664,17 +975,11 @@ namespace Ryujinx.Graphics.Gpu.Image SamplerPool samplerPool, TextureBindingInfo bindingInfo, bool isImage, - ref BufferBounds textureBufferBounds, out bool isNew) { - CacheEntryKey key = new CacheEntryKey( - isImage, - bindingInfo, - texturePool, - samplerPool, - ref textureBufferBounds); + CacheEntryFromPoolKey key = new CacheEntryFromPoolKey(isImage, bindingInfo, texturePool, samplerPool); - isNew = !_cache.TryGetValue(key, out CacheEntry entry); + isNew = !_cacheFromPool.TryGetValue(key, out CacheEntry entry); if (isNew) { @@ -684,13 +989,61 @@ namespace Ryujinx.Graphics.Gpu.Image { IImageArray array = _context.Renderer.CreateImageArray(arrayLength, bindingInfo.Target == Target.TextureBuffer); - _cache.Add(key, entry = new CacheEntry(ref key, array, texturePool, samplerPool)); + _cacheFromPool.Add(key, entry = new CacheEntry(array, texturePool, samplerPool)); } else { ITextureArray array = _context.Renderer.CreateTextureArray(arrayLength, bindingInfo.Target == Target.TextureBuffer); - _cache.Add(key, entry = new CacheEntry(ref key, array, texturePool, samplerPool)); + _cacheFromPool.Add(key, entry = new CacheEntry(array, texturePool, samplerPool)); + } + } + + return entry; + } + + /// + /// Gets a cached texture entry from constant buffer, or creates a new one if not found. + /// + /// Texture pool + /// Sampler pool + /// Array binding information + /// Whether the array is a image or texture array + /// Constant buffer bounds with the texture handles + /// Whether a new entry was created, or an existing one was returned + /// Cache entry + private CacheEntryFromBuffer GetOrAddEntry( + TexturePool texturePool, + SamplerPool samplerPool, + TextureBindingInfo bindingInfo, + bool isImage, + ref BufferBounds textureBufferBounds, + out bool isNew) + { + CacheEntryFromBufferKey key = new CacheEntryFromBufferKey( + isImage, + bindingInfo, + texturePool, + samplerPool, + ref textureBufferBounds); + + isNew = !_cacheFromBuffer.TryGetValue(key, out CacheEntryFromBuffer entry); + + if (isNew) + { + int arrayLength = bindingInfo.ArrayLength; + + if (isImage) + { + IImageArray array = _context.Renderer.CreateImageArray(arrayLength, bindingInfo.Target == Target.TextureBuffer); + + _cacheFromBuffer.Add(key, entry = new CacheEntryFromBuffer(ref key, array, texturePool, samplerPool)); + } + else + { + ITextureArray array = _context.Renderer.CreateTextureArray(arrayLength, bindingInfo.Target == Target.TextureBuffer); + + _cacheFromBuffer.Add(key, entry = new CacheEntryFromBuffer(ref key, array, texturePool, samplerPool)); } } @@ -716,15 +1069,52 @@ namespace Ryujinx.Graphics.Gpu.Image /// private void RemoveLeastUsedEntries() { - LinkedListNode nextNode = _lruCache.First; + LinkedListNode nextNode = _lruCache.First; while (nextNode != null && _currentTimestamp - nextNode.Value.CacheTimestamp >= MinDeltaForRemoval) { - LinkedListNode toRemove = nextNode; + LinkedListNode toRemove = nextNode; nextNode = nextNode.Next; - _cache.Remove(toRemove.Value.Key); + _cacheFromBuffer.Remove(toRemove.Value.Key); _lruCache.Remove(toRemove); } } + + /// + /// Removes all cached texture arrays matching the specified texture pool. + /// + /// Texture pool + public void RemoveAllWithPool(IPool pool) + { + List keysToRemove = null; + + foreach (CacheEntryFromPoolKey key in _cacheFromPool.Keys) + { + if (key.MatchesPool(pool)) + { + (keysToRemove ??= new()).Add(key); + } + } + + if (keysToRemove != null) + { + foreach (CacheEntryFromPoolKey key in keysToRemove) + { + _cacheFromPool.Remove(key); + } + } + } + + /// + /// Checks if a handle indicates the binding should have all its textures sourced directly from a pool. + /// + /// Handle to check + /// True if the handle represents direct pool access, false otherwise + private static bool IsDirectHandleType(int handle) + { + (_, _, TextureHandleType type) = TextureHandle.UnpackOffsets(handle); + + return type == TextureHandleType.Direct; + } } } diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs index a1dde673b9..9f1f60d956 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs @@ -34,7 +34,7 @@ namespace Ryujinx.Graphics.Gpu.Image private readonly TexturePoolCache _texturePoolCache; private readonly SamplerPoolCache _samplerPoolCache; - private readonly TextureBindingsArrayCache _arrayBindingsCache; + private readonly TextureBindingsArrayCache _bindingsArrayCache; private TexturePool _cachedTexturePool; private SamplerPool _cachedSamplerPool; @@ -72,12 +72,14 @@ namespace Ryujinx.Graphics.Gpu.Image /// /// The GPU context that the texture bindings manager belongs to /// The GPU channel that the texture bindings manager belongs to + /// Cache of texture array bindings /// Texture pools cache used to get texture pools from /// Sampler pools cache used to get sampler pools from /// True if the bindings manager is used for the compute engine public TextureBindingsManager( GpuContext context, GpuChannel channel, + TextureBindingsArrayCache bindingsArrayCache, TexturePoolCache texturePoolCache, SamplerPoolCache samplerPoolCache, bool isCompute) @@ -89,7 +91,7 @@ namespace Ryujinx.Graphics.Gpu.Image _isCompute = isCompute; - _arrayBindingsCache = new TextureBindingsArrayCache(context, channel, isCompute); + _bindingsArrayCache = bindingsArrayCache; int stages = isCompute ? 1 : Constants.ShaderStages; @@ -456,7 +458,7 @@ namespace Ryujinx.Graphics.Gpu.Image if (bindingInfo.ArrayLength > 1) { - _arrayBindingsCache.UpdateTextureArray(texturePool, samplerPool, stage, stageIndex, _textureBufferIndex, _samplerIndex, bindingInfo); + _bindingsArrayCache.UpdateTextureArray(texturePool, samplerPool, stage, stageIndex, _textureBufferIndex, _samplerIndex, bindingInfo); continue; } @@ -594,7 +596,7 @@ namespace Ryujinx.Graphics.Gpu.Image if (bindingInfo.ArrayLength > 1) { - _arrayBindingsCache.UpdateImageArray(pool, stage, stageIndex, _textureBufferIndex, bindingInfo); + _bindingsArrayCache.UpdateImageArray(pool, stage, stageIndex, _textureBufferIndex, bindingInfo); continue; } @@ -732,7 +734,7 @@ namespace Ryujinx.Graphics.Gpu.Image ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa); - TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId); + TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId, _bindingsArrayCache); TextureDescriptor descriptor; @@ -828,7 +830,7 @@ namespace Ryujinx.Graphics.Gpu.Image if (poolAddress != MemoryManager.PteUnmapped) { - texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, _texturePoolMaximumId); + texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, _texturePoolMaximumId, _bindingsArrayCache); _texturePool = texturePool; } } @@ -839,7 +841,7 @@ namespace Ryujinx.Graphics.Gpu.Image if (poolAddress != MemoryManager.PteUnmapped) { - samplerPool = _samplerPoolCache.FindOrCreate(_channel, poolAddress, _samplerPoolMaximumId); + samplerPool = _samplerPoolCache.FindOrCreate(_channel, poolAddress, _samplerPoolMaximumId, _bindingsArrayCache); _samplerPool = samplerPool; } } diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs index 8c2a887271..db2921468a 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs @@ -15,6 +15,7 @@ namespace Ryujinx.Graphics.Gpu.Image private readonly TextureBindingsManager _cpBindingsManager; private readonly TextureBindingsManager _gpBindingsManager; + private readonly TextureBindingsArrayCache _bindingsArrayCache; private readonly TexturePoolCache _texturePoolCache; private readonly SamplerPoolCache _samplerPoolCache; @@ -46,8 +47,9 @@ namespace Ryujinx.Graphics.Gpu.Image TexturePoolCache texturePoolCache = new(context); SamplerPoolCache samplerPoolCache = new(context); - _cpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, samplerPoolCache, isCompute: true); - _gpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, samplerPoolCache, isCompute: false); + _bindingsArrayCache = new TextureBindingsArrayCache(context, channel); + _cpBindingsManager = new TextureBindingsManager(context, channel, _bindingsArrayCache, texturePoolCache, samplerPoolCache, isCompute: true); + _gpBindingsManager = new TextureBindingsManager(context, channel, _bindingsArrayCache, texturePoolCache, samplerPoolCache, isCompute: false); _texturePoolCache = texturePoolCache; _samplerPoolCache = samplerPoolCache; @@ -384,7 +386,7 @@ namespace Ryujinx.Graphics.Gpu.Image { ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa); - TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId); + TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId, _bindingsArrayCache); return texturePool; } diff --git a/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs index 6e36753e85..a80dcbc875 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs @@ -58,7 +58,7 @@ namespace Ryujinx.Graphics.Gpu.Shader TextureBindings[i] = stage.Info.Textures.Select(descriptor => { - Target target = ShaderTexture.GetTarget(descriptor.Type); + Target target = descriptor.Type != SamplerType.None ? ShaderTexture.GetTarget(descriptor.Type) : default; var result = new TextureBindingInfo( target, @@ -66,7 +66,8 @@ namespace Ryujinx.Graphics.Gpu.Shader descriptor.ArrayLength, descriptor.CbufSlot, descriptor.HandleIndex, - descriptor.Flags); + descriptor.Flags, + descriptor.Type == SamplerType.None); if (descriptor.ArrayLength <= 1) { diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs index 681838a9b2..45f32e2d39 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs @@ -109,6 +109,13 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache return _oldSpecState.GraphicsState.HasConstantBufferDrawParameters; } + /// + /// Pool length is not available on the cache + public int QuerySamplerArrayLengthFromPool() + { + return QueryArrayLengthFromPool(isSampler: true); + } + /// public SamplerType QuerySamplerType(int handle, int cbufSlot) { @@ -117,6 +124,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache } /// + /// Constant buffer derived length is not available on the cache public int QueryTextureArrayLengthFromBuffer(int slot) { if (!_oldSpecState.TextureArrayFromBufferRegistered(_stageIndex, 0, slot)) @@ -130,6 +138,13 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache return arrayLength; } + /// + /// Pool length is not available on the cache + public int QueryTextureArrayLengthFromPool() + { + return QueryArrayLengthFromPool(isSampler: false); + } + /// public TextureFormat QueryTextureFormat(int handle, int cbufSlot) { @@ -170,6 +185,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache } /// + /// Texture information is not available on the cache public void RegisterTexture(int handle, int cbufSlot) { if (!_oldSpecState.TextureRegistered(_stageIndex, handle, cbufSlot)) @@ -182,5 +198,24 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache bool coordNormalized = _oldSpecState.GetCoordNormalized(_stageIndex, handle, cbufSlot); _newSpecState.RegisterTexture(_stageIndex, handle, cbufSlot, format, formatSrgb, target, coordNormalized); } + + /// + /// Gets the cached texture or sampler pool capacity. + /// + /// True to get sampler pool length, false for texture pool length + /// Pool length + /// Pool length is not available on the cache + private int QueryArrayLengthFromPool(bool isSampler) + { + if (!_oldSpecState.TextureArrayFromPoolRegistered(isSampler)) + { + throw new DiskCacheLoadException(DiskCacheLoadResult.MissingTextureArrayLength); + } + + int arrayLength = _oldSpecState.GetTextureArrayFromPoolLength(isSampler); + _newSpecState.RegisterTextureArrayLengthFromPool(isSampler, arrayLength); + + return arrayLength; + } } } diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs index b6a277a2af..2c19cc4b98 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs @@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMinor = 2; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; - private const uint CodeGenVersion = 6489; + private const uint CodeGenVersion = 6577; private const string SharedTocFileName = "shared.toc"; private const string SharedDataFileName = "shared.data"; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs index 1d22ab9332..04949690a2 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs @@ -120,6 +120,15 @@ namespace Ryujinx.Graphics.Gpu.Shader return _state.GraphicsState.HasUnalignedStorageBuffer || _state.ComputeState.HasUnalignedStorageBuffer; } + /// + public int QuerySamplerArrayLengthFromPool() + { + int length = _state.SamplerPoolMaximumId + 1; + _state.SpecializationState?.RegisterTextureArrayLengthFromPool(isSampler: true, length); + + return length; + } + /// public SamplerType QuerySamplerType(int handle, int cbufSlot) { @@ -141,6 +150,15 @@ namespace Ryujinx.Graphics.Gpu.Shader return arrayLength; } + /// + public int QueryTextureArrayLengthFromPool() + { + int length = _state.PoolState.TexturePoolMaximumId + 1; + _state.SpecializationState?.RegisterTextureArrayLengthFromPool(isSampler: false, length); + + return length; + } + //// public TextureFormat QueryTextureFormat(int handle, int cbufSlot) { diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs index 06e5edf1eb..0d562b0da2 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs @@ -213,6 +213,8 @@ namespace Ryujinx.Graphics.Gpu.Shader public bool QueryHostSupportsScaledVertexFormats() => _context.Capabilities.SupportsScaledVertexFormats; + public bool QueryHostSupportsSeparateSampler() => _context.Capabilities.SupportsSeparateSampler; + public bool QueryHostSupportsShaderBallot() => _context.Capabilities.SupportsShaderBallot; public bool QueryHostSupportsShaderBarrierDivergence() => _context.Capabilities.SupportsShaderBarrierDivergence; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorState.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorState.cs index cfc4a2cccc..808bf18514 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorState.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorState.cs @@ -5,6 +5,11 @@ namespace Ryujinx.Graphics.Gpu.Shader /// class GpuAccessorState { + /// + /// Maximum ID that a sampler pool entry may have. + /// + public readonly int SamplerPoolMaximumId; + /// /// GPU texture pool state. /// @@ -38,18 +43,21 @@ namespace Ryujinx.Graphics.Gpu.Shader /// /// Creates a new GPU accessor state. /// + /// Maximum ID that a sampler pool entry may have /// GPU texture pool state /// GPU compute state, for compute shaders /// GPU graphics state, for vertex, tessellation, geometry and fragment shaders /// Shader specialization state (shared by all stages) /// Transform feedback information, if the shader uses transform feedback. Otherwise, should be null public GpuAccessorState( + int samplerPoolMaximumId, GpuChannelPoolState poolState, GpuChannelComputeState computeState, GpuChannelGraphicsState graphicsState, ShaderSpecializationState specializationState, TransformFeedbackDescriptor[] transformFeedbackDescriptors = null) { + SamplerPoolMaximumId = samplerPoolMaximumId; PoolState = poolState; GraphicsState = graphicsState; ComputeState = computeState; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuChannelPoolState.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuChannelPoolState.cs index ddb45152ef..a2ab993358 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/GpuChannelPoolState.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuChannelPoolState.cs @@ -2,7 +2,6 @@ using System; namespace Ryujinx.Graphics.Gpu.Shader { -#pragma warning disable CS0659 // Class overrides Object.Equals(object o) but does not override Object.GetHashCode() /// /// State used by the . /// @@ -52,6 +51,10 @@ namespace Ryujinx.Graphics.Gpu.Shader { return obj is GpuChannelPoolState state && Equals(state); } + + public override int GetHashCode() + { + return HashCode.Combine(TexturePoolGpuVa, TexturePoolMaximumId, TextureBufferIndex); + } } -#pragma warning restore CS0659 } diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs index 0b17af8b27..31cc94a25c 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs @@ -192,12 +192,14 @@ namespace Ryujinx.Graphics.Gpu.Shader /// This automatically translates, compiles and adds the code to the cache if not present. /// /// GPU channel + /// Maximum ID that an entry in the sampler pool may have /// Texture pool state /// Compute engine state /// GPU virtual address of the binary shader code /// Compiled compute shader code public CachedShaderProgram GetComputeShader( GpuChannel channel, + int samplerPoolMaximumId, GpuChannelPoolState poolState, GpuChannelComputeState computeState, ulong gpuVa) @@ -214,7 +216,7 @@ namespace Ryujinx.Graphics.Gpu.Shader } ShaderSpecializationState specState = new(ref computeState); - GpuAccessorState gpuAccessorState = new(poolState, computeState, default, specState); + GpuAccessorState gpuAccessorState = new(samplerPoolMaximumId, poolState, computeState, default, specState); GpuAccessor gpuAccessor = new(_context, channel, gpuAccessorState); gpuAccessor.InitializeReservedCounts(tfEnabled: false, vertexAsCompute: false); @@ -291,6 +293,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// GPU state /// Pipeline state /// GPU channel + /// Maximum ID that an entry in the sampler pool may have /// Texture pool state /// 3D engine state /// Addresses of the shaders for each stage @@ -299,6 +302,7 @@ namespace Ryujinx.Graphics.Gpu.Shader ref ThreedClassState state, ref ProgramPipelineState pipeline, GpuChannel channel, + int samplerPoolMaximumId, ref GpuChannelPoolState poolState, ref GpuChannelGraphicsState graphicsState, ShaderAddresses addresses) @@ -319,7 +323,7 @@ namespace Ryujinx.Graphics.Gpu.Shader UpdatePipelineInfo(ref state, ref pipeline, graphicsState, channel); ShaderSpecializationState specState = new(ref graphicsState, ref pipeline, transformFeedbackDescriptors); - GpuAccessorState gpuAccessorState = new(poolState, default, graphicsState, specState, transformFeedbackDescriptors); + GpuAccessorState gpuAccessorState = new(samplerPoolMaximumId, poolState, default, graphicsState, specState, transformFeedbackDescriptors); ReadOnlySpan addressesSpan = addresses.AsSpan(); diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs index ea8f164f10..ed56db3b30 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs @@ -185,11 +185,7 @@ namespace Ryujinx.Graphics.Gpu.Shader { if (texture.ArrayLength > 1) { - bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer; - - ResourceType type = isBuffer - ? (isImage ? ResourceType.BufferImage : ResourceType.BufferTexture) - : (isImage ? ResourceType.Image : ResourceType.TextureAndSampler); + ResourceType type = GetTextureResourceType(texture, isImage); _resourceDescriptors[setIndex].Add(new ResourceDescriptor(texture.Binding, texture.ArrayLength, type, stages)); } @@ -242,16 +238,38 @@ namespace Ryujinx.Graphics.Gpu.Shader { foreach (TextureDescriptor texture in textures) { - bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer; - - ResourceType type = isBuffer - ? (isImage ? ResourceType.BufferImage : ResourceType.BufferTexture) - : (isImage ? ResourceType.Image : ResourceType.TextureAndSampler); + ResourceType type = GetTextureResourceType(texture, isImage); _resourceUsages[setIndex].Add(new ResourceUsage(texture.Binding, texture.ArrayLength, type, stages)); } } + private static ResourceType GetTextureResourceType(TextureDescriptor texture, bool isImage) + { + bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer; + + if (isBuffer) + { + return isImage ? ResourceType.BufferImage : ResourceType.BufferTexture; + } + else if (isImage) + { + return ResourceType.Image; + } + else if (texture.Type == SamplerType.None) + { + return ResourceType.Sampler; + } + else if (texture.Separate) + { + return ResourceType.Texture; + } + else + { + return ResourceType.TextureAndSampler; + } + } + /// /// Creates a new shader information structure from the added information. /// diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs index c90a0b8f4b..98acb6f27d 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs @@ -31,6 +31,7 @@ namespace Ryujinx.Graphics.Gpu.Shader PrimitiveTopology = 1 << 1, TransformFeedback = 1 << 3, TextureArrayFromBuffer = 1 << 4, + TextureArrayFromPool = 1 << 5, } private QueriedStateFlags _queriedState; @@ -154,7 +155,8 @@ namespace Ryujinx.Graphics.Gpu.Shader } private readonly Dictionary> _textureSpecialization; - private readonly Dictionary _textureArraySpecialization; + private readonly Dictionary _textureArrayFromBufferSpecialization; + private readonly Dictionary _textureArrayFromPoolSpecialization; private KeyValuePair>[] _allTextures; private Box[][] _textureByBinding; private Box[][] _imageByBinding; @@ -165,7 +167,8 @@ namespace Ryujinx.Graphics.Gpu.Shader private ShaderSpecializationState() { _textureSpecialization = new Dictionary>(); - _textureArraySpecialization = new Dictionary(); + _textureArrayFromBufferSpecialization = new Dictionary(); + _textureArrayFromPoolSpecialization = new Dictionary(); } /// @@ -327,7 +330,7 @@ namespace Ryujinx.Graphics.Gpu.Shader } /// - /// Indicates that the coordinate normalization state of a given texture was used during the shader translation process. + /// Registers the length of a texture array calculated from a constant buffer size. /// /// Shader stage where the texture is used /// Offset in words of the texture handle on the texture buffer @@ -335,10 +338,21 @@ namespace Ryujinx.Graphics.Gpu.Shader /// Number of elements in the texture array public void RegisterTextureArrayLengthFromBuffer(int stageIndex, int handle, int cbufSlot, int length) { - _textureArraySpecialization[new TextureKey(stageIndex, handle, cbufSlot)] = length; + _textureArrayFromBufferSpecialization[new TextureKey(stageIndex, handle, cbufSlot)] = length; _queriedState |= QueriedStateFlags.TextureArrayFromBuffer; } + /// + /// Registers the length of a texture array calculated from a texture or sampler pool capacity. + /// + /// True for sampler pool, false for texture pool + /// Number of elements in the texture array + public void RegisterTextureArrayLengthFromPool(bool isSampler, int length) + { + _textureArrayFromPoolSpecialization[isSampler] = length; + _queriedState |= QueriedStateFlags.TextureArrayFromPool; + } + /// /// Indicates that the format of a given texture was used during the shader translation process. /// @@ -385,7 +399,7 @@ namespace Ryujinx.Graphics.Gpu.Shader } /// - /// Checks if a given texture was registerd on this specialization state. + /// Checks if a given texture was registered on this specialization state. /// /// Shader stage where the texture is used /// Offset in words of the texture handle on the texture buffer @@ -396,14 +410,25 @@ namespace Ryujinx.Graphics.Gpu.Shader } /// - /// Checks if a given texture array (from constant buffer) was registerd on this specialization state. + /// Checks if a given texture array (from constant buffer) was registered on this specialization state. /// /// Shader stage where the texture is used /// Offset in words of the texture handle on the texture buffer /// Slot of the texture buffer constant buffer + /// True if the length for the given buffer and stage exists, false otherwise public bool TextureArrayFromBufferRegistered(int stageIndex, int handle, int cbufSlot) { - return _textureArraySpecialization.ContainsKey(new TextureKey(stageIndex, handle, cbufSlot)); + return _textureArrayFromBufferSpecialization.ContainsKey(new TextureKey(stageIndex, handle, cbufSlot)); + } + + /// + /// Checks if a given texture array (from a sampler pool or texture pool) was registered on this specialization state. + /// + /// True for sampler pool, false for texture pool + /// True if the length for the given pool, false otherwise + public bool TextureArrayFromPoolRegistered(bool isSampler) + { + return _textureArrayFromPoolSpecialization.ContainsKey(isSampler); } /// @@ -412,6 +437,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// Shader stage where the texture is used /// Offset in words of the texture handle on the texture buffer /// Slot of the texture buffer constant buffer + /// Format and sRGB tuple public (uint, bool) GetFormat(int stageIndex, int handle, int cbufSlot) { TextureSpecializationState state = GetTextureSpecState(stageIndex, handle, cbufSlot).Value; @@ -424,6 +450,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// Shader stage where the texture is used /// Offset in words of the texture handle on the texture buffer /// Slot of the texture buffer constant buffer + /// Texture target public TextureTarget GetTextureTarget(int stageIndex, int handle, int cbufSlot) { return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.TextureTarget; @@ -435,6 +462,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// Shader stage where the texture is used /// Offset in words of the texture handle on the texture buffer /// Slot of the texture buffer constant buffer + /// True if coordinates are normalized, false otherwise public bool GetCoordNormalized(int stageIndex, int handle, int cbufSlot) { return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.CoordNormalized; @@ -446,9 +474,20 @@ namespace Ryujinx.Graphics.Gpu.Shader /// Shader stage where the texture is used /// Offset in words of the texture handle on the texture buffer /// Slot of the texture buffer constant buffer + /// Texture array length public int GetTextureArrayFromBufferLength(int stageIndex, int handle, int cbufSlot) { - return _textureArraySpecialization[new TextureKey(stageIndex, handle, cbufSlot)]; + return _textureArrayFromBufferSpecialization[new TextureKey(stageIndex, handle, cbufSlot)]; + } + + /// + /// Gets the recorded length of a given texture array (from a sampler or texture pool). + /// + /// True to get the sampler pool length, false to get the texture pool length + /// Texture array length + public int GetTextureArrayFromPoolLength(bool isSampler) + { + return _textureArrayFromPoolSpecialization[isSampler]; } /// @@ -894,7 +933,23 @@ namespace Ryujinx.Graphics.Gpu.Shader dataReader.ReadWithMagicAndSize(ref textureKey, TexkMagic); dataReader.Read(ref length); - specState._textureArraySpecialization[textureKey] = length; + specState._textureArrayFromBufferSpecialization[textureKey] = length; + } + } + + if (specState._queriedState.HasFlag(QueriedStateFlags.TextureArrayFromPool)) + { + dataReader.Read(ref count); + + for (int index = 0; index < count; index++) + { + bool textureKey = default; + int length = 0; + + dataReader.ReadWithMagicAndSize(ref textureKey, TexkMagic); + dataReader.Read(ref length); + + specState._textureArrayFromPoolSpecialization[textureKey] = length; } } @@ -965,10 +1020,25 @@ namespace Ryujinx.Graphics.Gpu.Shader if (_queriedState.HasFlag(QueriedStateFlags.TextureArrayFromBuffer)) { - count = (ushort)_textureArraySpecialization.Count; + count = (ushort)_textureArrayFromBufferSpecialization.Count; dataWriter.Write(ref count); - foreach (var kv in _textureArraySpecialization) + foreach (var kv in _textureArrayFromBufferSpecialization) + { + var textureKey = kv.Key; + var length = kv.Value; + + dataWriter.WriteWithMagicAndSize(ref textureKey, TexkMagic); + dataWriter.Write(ref length); + } + } + + if (_queriedState.HasFlag(QueriedStateFlags.TextureArrayFromPool)) + { + count = (ushort)_textureArrayFromPoolSpecialization.Count; + dataWriter.Write(ref count); + + foreach (var kv in _textureArrayFromPoolSpecialization) { var textureKey = kv.Key; var length = kv.Value; diff --git a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs index a945cbf202..d56c40af46 100644 --- a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs +++ b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs @@ -176,6 +176,7 @@ namespace Ryujinx.Graphics.OpenGL supportsCubemapView: true, supportsNonConstantTextureOffset: HwCapabilities.SupportsNonConstantTextureOffset, supportsScaledVertexFormats: true, + supportsSeparateSampler: false, supportsShaderBallot: HwCapabilities.SupportsShaderBallot, supportsShaderBarrierDivergence: !(intelWindows || intelUnix), supportsShaderFloat64: true, diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs index 763487dac6..eb6c689b88 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Numerics; namespace Ryujinx.Graphics.Shader.CodeGen.Glsl { @@ -352,7 +351,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl arrayDecl = "[]"; } - string samplerTypeName = definition.Type.ToGlslSamplerType(); + string samplerTypeName = definition.Separate ? definition.Type.ToGlslTextureType() : definition.Type.ToGlslSamplerType(); string layout = string.Empty; diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs index b4773b819c..f0e57b534b 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs @@ -639,14 +639,27 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions private static string GetSamplerName(CodeGenContext context, AstTextureOperation texOp, ref int srcIndex) { - TextureDefinition definition = context.Properties.Textures[texOp.Binding]; - string name = definition.Name; + TextureDefinition textureDefinition = context.Properties.Textures[texOp.Binding]; + string name = textureDefinition.Name; - if (definition.ArrayLength != 1) + if (textureDefinition.ArrayLength != 1) { name = $"{name}[{GetSourceExpr(context, texOp.GetSource(srcIndex++), AggregateType.S32)}]"; } + if (texOp.IsSeparate) + { + TextureDefinition samplerDefinition = context.Properties.Textures[texOp.SamplerBinding]; + string samplerName = samplerDefinition.Name; + + if (samplerDefinition.ArrayLength != 1) + { + samplerName = $"{samplerName}[{GetSourceExpr(context, texOp.GetSource(srcIndex++), AggregateType.S32)}]"; + } + + name = $"{texOp.Type.ToGlslSamplerType()}({name}, {samplerName})"; + } + return name; } diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs index 9633c522ea..37df4df802 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs @@ -160,37 +160,49 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv { int setIndex = context.TargetApi == TargetApi.Vulkan ? sampler.Set : 0; - var dim = (sampler.Type & SamplerType.Mask) switch + SpvInstruction imageType; + SpvInstruction sampledImageType; + + if (sampler.Type != SamplerType.None) { - SamplerType.Texture1D => Dim.Dim1D, - SamplerType.Texture2D => Dim.Dim2D, - SamplerType.Texture3D => Dim.Dim3D, - SamplerType.TextureCube => Dim.Cube, - SamplerType.TextureBuffer => Dim.Buffer, - _ => throw new InvalidOperationException($"Invalid sampler type \"{sampler.Type & SamplerType.Mask}\"."), - }; + var dim = (sampler.Type & SamplerType.Mask) switch + { + SamplerType.Texture1D => Dim.Dim1D, + SamplerType.Texture2D => Dim.Dim2D, + SamplerType.Texture3D => Dim.Dim3D, + SamplerType.TextureCube => Dim.Cube, + SamplerType.TextureBuffer => Dim.Buffer, + _ => throw new InvalidOperationException($"Invalid sampler type \"{sampler.Type & SamplerType.Mask}\"."), + }; - var imageType = context.TypeImage( - context.TypeFP32(), - dim, - sampler.Type.HasFlag(SamplerType.Shadow), - sampler.Type.HasFlag(SamplerType.Array), - sampler.Type.HasFlag(SamplerType.Multisample), - 1, - ImageFormat.Unknown); + imageType = context.TypeImage( + context.TypeFP32(), + dim, + sampler.Type.HasFlag(SamplerType.Shadow), + sampler.Type.HasFlag(SamplerType.Array), + sampler.Type.HasFlag(SamplerType.Multisample), + 1, + ImageFormat.Unknown); - var sampledImageType = context.TypeSampledImage(imageType); - var sampledImagePointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageType); + sampledImageType = context.TypeSampledImage(imageType); + } + else + { + imageType = sampledImageType = context.TypeSampler(); + } + + var sampledOrSeparateImageType = sampler.Separate ? imageType : sampledImageType; + var sampledImagePointerType = context.TypePointer(StorageClass.UniformConstant, sampledOrSeparateImageType); var sampledImageArrayPointerType = sampledImagePointerType; if (sampler.ArrayLength == 0) { - var sampledImageArrayType = context.TypeRuntimeArray(sampledImageType); + var sampledImageArrayType = context.TypeRuntimeArray(sampledOrSeparateImageType); sampledImageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageArrayType); } else if (sampler.ArrayLength != 1) { - var sampledImageArrayType = context.TypeArray(sampledImageType, context.Constant(context.TypeU32(), sampler.ArrayLength)); + var sampledImageArrayType = context.TypeArray(sampledOrSeparateImageType, context.Constant(context.TypeU32(), sampler.ArrayLength)); sampledImageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageArrayType); } diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs index 409e466cd7..34f8532a65 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs @@ -838,16 +838,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv } SamplerDeclaration declaration = context.Samplers[texOp.Binding]; - SpvInstruction image = declaration.Image; - - if (declaration.IsIndexed) - { - SpvInstruction textureIndex = Src(AggregateType.S32); - - image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex); - } - - image = context.Load(declaration.SampledImageType, image); + SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex); int pCount = texOp.Type.GetDimensions(); @@ -1171,16 +1162,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv } SamplerDeclaration declaration = context.Samplers[texOp.Binding]; - SpvInstruction image = declaration.Image; - - if (declaration.IsIndexed) - { - SpvInstruction textureIndex = Src(AggregateType.S32); - - image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex); - } - - image = context.Load(declaration.SampledImageType, image); + SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex); int coordsCount = texOp.Type.GetDimensions(); @@ -1449,17 +1431,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv { AstTextureOperation texOp = (AstTextureOperation)operation; + int srcIndex = 0; + SamplerDeclaration declaration = context.Samplers[texOp.Binding]; - SpvInstruction image = declaration.Image; + SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex); - if (declaration.IsIndexed) - { - SpvInstruction textureIndex = context.GetS32(texOp.GetSource(0)); - - image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex); - } - - image = context.Load(declaration.SampledImageType, image); image = context.Image(declaration.ImageType, image); SpvInstruction result = context.ImageQuerySamples(context.TypeS32(), image); @@ -1471,17 +1447,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv { AstTextureOperation texOp = (AstTextureOperation)operation; + int srcIndex = 0; + SamplerDeclaration declaration = context.Samplers[texOp.Binding]; - SpvInstruction image = declaration.Image; + SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex); - if (declaration.IsIndexed) - { - SpvInstruction textureIndex = context.GetS32(texOp.GetSource(0)); - - image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex); - } - - image = context.Load(declaration.SampledImageType, image); image = context.Image(declaration.ImageType, image); if (texOp.Index == 3) @@ -1506,8 +1476,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv if (hasLod) { - int lodSrcIndex = declaration.IsIndexed ? 1 : 0; - var lod = context.GetS32(operation.GetSource(lodSrcIndex)); + var lod = context.GetS32(operation.GetSource(srcIndex)); result = context.ImageQuerySizeLod(resultType, image, lod); } else @@ -1905,6 +1874,43 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv } } + private static SpvInstruction GenerateSampledImageLoad(CodeGenContext context, AstTextureOperation texOp, SamplerDeclaration declaration, ref int srcIndex) + { + SpvInstruction image = declaration.Image; + + if (declaration.IsIndexed) + { + SpvInstruction textureIndex = context.Get(AggregateType.S32, texOp.GetSource(srcIndex++)); + + image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex); + } + + if (texOp.IsSeparate) + { + image = context.Load(declaration.ImageType, image); + + SamplerDeclaration samplerDeclaration = context.Samplers[texOp.SamplerBinding]; + + SpvInstruction sampler = samplerDeclaration.Image; + + if (samplerDeclaration.IsIndexed) + { + SpvInstruction samplerIndex = context.Get(AggregateType.S32, texOp.GetSource(srcIndex++)); + + sampler = context.AccessChain(samplerDeclaration.SampledImagePointerType, sampler, samplerIndex); + } + + sampler = context.Load(samplerDeclaration.ImageType, sampler); + image = context.SampledImage(declaration.SampledImageType, image, sampler); + } + else + { + image = context.Load(declaration.SampledImageType, image); + } + + return image; + } + private static OperationResult GenerateUnary( CodeGenContext context, AstOperation operation, diff --git a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs index 99366ad67e..b1a9f9f842 100644 --- a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs +++ b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs @@ -26,13 +26,6 @@ namespace Ryujinx.Graphics.Shader /// Span of the memory location ReadOnlySpan GetCode(ulong address, int minimumSize); - /// - /// Gets the size in bytes of a bound constant buffer for the current shader stage. - /// - /// The number of the constant buffer to get the size from - /// Size in bytes - int QueryTextureArrayLengthFromBuffer(int slot); - /// /// Queries the binding number of a constant buffer. /// @@ -298,6 +291,15 @@ namespace Ryujinx.Graphics.Shader return true; } + /// + /// Queries host API support for separate textures and samplers. + /// + /// True if the API supports samplers and textures to be combined on the shader, false otherwise + bool QueryHostSupportsSeparateSampler() + { + return true; + } + /// /// Queries host GPU shader ballot support. /// @@ -388,6 +390,12 @@ namespace Ryujinx.Graphics.Shader return true; } + /// + /// Gets the maximum number of samplers that the bound texture pool may have. + /// + /// Maximum amount of samplers that the pool may have + int QuerySamplerArrayLengthFromPool(); + /// /// Queries sampler type information. /// @@ -399,6 +407,19 @@ namespace Ryujinx.Graphics.Shader return SamplerType.Texture2D; } + /// + /// Gets the size in bytes of a bound constant buffer for the current shader stage. + /// + /// The number of the constant buffer to get the size from + /// Size in bytes + int QueryTextureArrayLengthFromBuffer(int slot); + + /// + /// Gets the maximum number of textures that the bound texture pool may have. + /// + /// Maximum amount of textures that the pool may have + int QueryTextureArrayLengthFromPool(); + /// /// Queries texture coordinate normalization information. /// diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs index 0c1b2a3f35..713e8a4fb7 100644 --- a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs +++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs @@ -216,6 +216,11 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation newSources[index] = source; + if (source != null && source.Type == OperandType.LocalVariable) + { + source.UseOps.Add(this); + } + _sources = newSources; } diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs index 1b82e2945b..74ec5ca611 100644 --- a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs +++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs @@ -9,6 +9,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation public TextureFlags Flags { get; private set; } public int Binding { get; private set; } + public int SamplerBinding { get; private set; } public TextureOperation( Instruction inst, @@ -24,6 +25,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation Format = format; Flags = flags; Binding = binding; + SamplerBinding = -1; } public void TurnIntoArray(int binding) @@ -32,6 +34,13 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation Binding = binding; } + public void TurnIntoArray(int textureBinding, int samplerBinding) + { + TurnIntoArray(textureBinding); + + SamplerBinding = samplerBinding; + } + public void SetBinding(int binding) { if ((Flags & TextureFlags.Bindless) != 0) diff --git a/src/Ryujinx.Graphics.Shader/SamplerType.cs b/src/Ryujinx.Graphics.Shader/SamplerType.cs index 66c748bf3a..a693495fa5 100644 --- a/src/Ryujinx.Graphics.Shader/SamplerType.cs +++ b/src/Ryujinx.Graphics.Shader/SamplerType.cs @@ -69,6 +69,7 @@ namespace Ryujinx.Graphics.Shader { string typeName = (type & SamplerType.Mask) switch { + SamplerType.None => "sampler", SamplerType.Texture1D => "sampler1D", SamplerType.TextureBuffer => "samplerBuffer", SamplerType.Texture2D => "sampler2D", @@ -95,6 +96,31 @@ namespace Ryujinx.Graphics.Shader return typeName; } + public static string ToGlslTextureType(this SamplerType type) + { + string typeName = (type & SamplerType.Mask) switch + { + SamplerType.Texture1D => "texture1D", + SamplerType.TextureBuffer => "textureBuffer", + SamplerType.Texture2D => "texture2D", + SamplerType.Texture3D => "texture3D", + SamplerType.TextureCube => "textureCube", + _ => throw new ArgumentException($"Invalid texture type \"{type}\"."), + }; + + if ((type & SamplerType.Multisample) != 0) + { + typeName += "MS"; + } + + if ((type & SamplerType.Array) != 0) + { + typeName += "Array"; + } + + return typeName; + } + public static string ToGlslImageType(this SamplerType type, AggregateType componentType) { string typeName = (type & SamplerType.Mask) switch diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs index 3970df1e94..4068c41271 100644 --- a/src/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs @@ -9,6 +9,9 @@ namespace Ryujinx.Graphics.Shader.StructuredIr public TextureFlags Flags { get; } public int Binding { get; } + public int SamplerBinding { get; } + + public bool IsSeparate => SamplerBinding >= 0; public AstTextureOperation( Instruction inst, @@ -16,6 +19,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr TextureFormat format, TextureFlags flags, int binding, + int samplerBinding, int index, params IAstNode[] sources) : base(inst, StorageKind.None, false, index, sources, sources.Length) { @@ -23,6 +27,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr Format = format; Flags = flags; Binding = binding; + SamplerBinding = samplerBinding; } } } diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs index 2e2df75468..c4ebaee73f 100644 --- a/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs @@ -169,7 +169,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr AstTextureOperation GetAstTextureOperation(TextureOperation texOp) { - return new AstTextureOperation(inst, texOp.Type, texOp.Format, texOp.Flags, texOp.Binding, texOp.Index, sources); + return new AstTextureOperation(inst, texOp.Type, texOp.Format, texOp.Flags, texOp.Binding, texOp.SamplerBinding, texOp.Index, sources); } int componentsCount = BitOperations.PopCount((uint)operation.Index); diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/TextureDefinition.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/TextureDefinition.cs index bdd3a2ed14..1021dff0e8 100644 --- a/src/Ryujinx.Graphics.Shader/StructuredIr/TextureDefinition.cs +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/TextureDefinition.cs @@ -5,25 +5,39 @@ namespace Ryujinx.Graphics.Shader public int Set { get; } public int Binding { get; } public int ArrayLength { get; } + public bool Separate { get; } public string Name { get; } public SamplerType Type { get; } public TextureFormat Format { get; } public TextureUsageFlags Flags { get; } - public TextureDefinition(int set, int binding, int arrayLength, string name, SamplerType type, TextureFormat format, TextureUsageFlags flags) + public TextureDefinition( + int set, + int binding, + int arrayLength, + bool separate, + string name, + SamplerType type, + TextureFormat format, + TextureUsageFlags flags) { Set = set; Binding = binding; ArrayLength = arrayLength; + Separate = separate; Name = name; Type = type; Format = format; Flags = flags; } + public TextureDefinition(int set, int binding, string name, SamplerType type) : this(set, binding, 1, false, name, type, TextureFormat.Unknown, TextureUsageFlags.None) + { + } + public TextureDefinition SetFlag(TextureUsageFlags flag) { - return new TextureDefinition(Set, Binding, ArrayLength, Name, Type, Format, Flags | flag); + return new TextureDefinition(Set, Binding, ArrayLength, Separate, Name, Type, Format, Flags | flag); } } } diff --git a/src/Ryujinx.Graphics.Shader/TextureDescriptor.cs b/src/Ryujinx.Graphics.Shader/TextureDescriptor.cs index 38834da726..d287a1aa7b 100644 --- a/src/Ryujinx.Graphics.Shader/TextureDescriptor.cs +++ b/src/Ryujinx.Graphics.Shader/TextureDescriptor.cs @@ -13,6 +13,8 @@ namespace Ryujinx.Graphics.Shader public readonly int HandleIndex; public readonly int ArrayLength; + public readonly bool Separate; + public readonly TextureUsageFlags Flags; public TextureDescriptor( @@ -22,6 +24,7 @@ namespace Ryujinx.Graphics.Shader int cbufSlot, int handleIndex, int arrayLength, + bool separate, TextureUsageFlags flags) { Binding = binding; @@ -30,6 +33,7 @@ namespace Ryujinx.Graphics.Shader CbufSlot = cbufSlot; HandleIndex = handleIndex; ArrayLength = arrayLength; + Separate = separate; Flags = flags; } } diff --git a/src/Ryujinx.Graphics.Shader/TextureHandle.cs b/src/Ryujinx.Graphics.Shader/TextureHandle.cs index 7df9c8e47b..3aaceac480 100644 --- a/src/Ryujinx.Graphics.Shader/TextureHandle.cs +++ b/src/Ryujinx.Graphics.Shader/TextureHandle.cs @@ -9,6 +9,7 @@ namespace Ryujinx.Graphics.Shader SeparateSamplerHandle = 1, SeparateSamplerId = 2, SeparateConstantSamplerHandle = 3, + Direct = 4, } public static class TextureHandle diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs index ad955278fe..223215439f 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs @@ -1,6 +1,7 @@ using Ryujinx.Graphics.Shader.Instructions; using Ryujinx.Graphics.Shader.IntermediateRepresentation; using Ryujinx.Graphics.Shader.StructuredIr; +using System; using System.Collections.Generic; namespace Ryujinx.Graphics.Shader.Translation.Optimizations @@ -31,7 +32,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations continue; } - if (!TryConvertBindless(block, resourceManager, gpuAccessor, texOp)) + if (!TryConvertBindless(block, resourceManager, gpuAccessor, texOp) && + !GenerateBindlessAccess(block, resourceManager, gpuAccessor, texOp, node)) { // If we can't do bindless elimination, remove the texture operation. // Set any destination variables to zero. @@ -46,6 +48,88 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations } } + private static bool GenerateBindlessAccess( + BasicBlock block, + ResourceManager resourceManager, + IGpuAccessor gpuAccessor, + TextureOperation texOp, + LinkedListNode node) + { + if (!gpuAccessor.QueryHostSupportsSeparateSampler()) + { + // We depend on combining samplers and textures in the shader being supported for this. + + return false; + } + + Operand nvHandle = texOp.GetSource(0); + + if (nvHandle.AsgOp is not Operation handleOp || + handleOp.Inst != Instruction.Load || + handleOp.StorageKind != StorageKind.Input) + { + // Right now, we only allow bindless access when the handle comes from a shader input. + // This is an artificial limitation to prevent it from being used in cases where it + // would have a large performance impact of loading all textures in the pool. + // It might be removed in the future, if we can mitigate the performance impact. + + return false; + } + + Operand textureHandle = OperandHelper.Local(); + Operand samplerHandle = OperandHelper.Local(); + Operand textureIndex = OperandHelper.Local(); + + block.Operations.AddBefore(node, new Operation(Instruction.BitwiseAnd, textureHandle, nvHandle, OperandHelper.Const(0xfffff))); + block.Operations.AddBefore(node, new Operation(Instruction.ShiftRightU32, samplerHandle, nvHandle, OperandHelper.Const(20))); + + int texturePoolLength = Math.Max(BindlessToArray.MinimumArrayLength, gpuAccessor.QueryTextureArrayLengthFromPool()); + + block.Operations.AddBefore(node, new Operation(Instruction.MinimumU32, textureIndex, textureHandle, OperandHelper.Const(texturePoolLength - 1))); + + texOp.SetSource(0, textureIndex); + + bool hasSampler = !texOp.Inst.IsImage(); + + int textureBinding = resourceManager.GetTextureOrImageBinding( + texOp.Inst, + texOp.Type, + texOp.Format, + texOp.Flags & ~TextureFlags.Bindless, + 0, + TextureHandle.PackOffsets(0, 0, TextureHandleType.Direct), + texturePoolLength, + hasSampler); + + if (hasSampler) + { + Operand samplerIndex = OperandHelper.Local(); + + int samplerPoolLength = Math.Max(BindlessToArray.MinimumArrayLength, gpuAccessor.QuerySamplerArrayLengthFromPool()); + + block.Operations.AddBefore(node, new Operation(Instruction.MinimumU32, samplerIndex, samplerHandle, OperandHelper.Const(samplerPoolLength - 1))); + + texOp.InsertSource(1, samplerIndex); + + int samplerBinding = resourceManager.GetTextureOrImageBinding( + texOp.Inst, + SamplerType.None, + texOp.Format, + TextureFlags.None, + 0, + TextureHandle.PackOffsets(0, 0, TextureHandleType.Direct), + samplerPoolLength); + + texOp.TurnIntoArray(textureBinding, samplerBinding); + } + else + { + texOp.TurnIntoArray(textureBinding); + } + + return true; + } + private static bool TryConvertBindless(BasicBlock block, ResourceManager resourceManager, IGpuAccessor gpuAccessor, TextureOperation texOp) { if (texOp.Inst == Instruction.TextureSample || texOp.Inst.IsTextureQuery()) diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs index 7543d1c242..f2be7975da 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs @@ -11,7 +11,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations private const int HardcodedArrayLengthOgl = 4; // 1 and 0 elements are not considered arrays anymore. - private const int MinimumArrayLength = 2; + public const int MinimumArrayLength = 2; public static void RunPassOgl(BasicBlock block, ResourceManager resourceManager) { diff --git a/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs b/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs index e9fe0b1ee5..890501c919 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs @@ -29,7 +29,7 @@ namespace Ryujinx.Graphics.Shader.Translation private readonly HashSet _usedConstantBufferBindings; - private readonly record struct TextureInfo(int CbufSlot, int Handle, int ArrayLength, SamplerType Type, TextureFormat Format); + private readonly record struct TextureInfo(int CbufSlot, int Handle, int ArrayLength, bool Separate, SamplerType Type, TextureFormat Format); private struct TextureMeta { @@ -225,7 +225,8 @@ namespace Ryujinx.Graphics.Shader.Translation TextureFlags flags, int cbufSlot, int handle, - int arrayLength = 1) + int arrayLength = 1, + bool separate = false) { inst &= Instruction.Mask; bool isImage = inst.IsImage(); @@ -239,7 +240,18 @@ namespace Ryujinx.Graphics.Shader.Translation format = TextureFormat.Unknown; } - int binding = GetTextureOrImageBinding(cbufSlot, handle, arrayLength, type, format, isImage, intCoords, isWrite, accurateType, coherent); + int binding = GetTextureOrImageBinding( + cbufSlot, + handle, + arrayLength, + type, + format, + isImage, + intCoords, + isWrite, + accurateType, + coherent, + separate); _gpuAccessor.RegisterTexture(handle, cbufSlot); @@ -256,9 +268,10 @@ namespace Ryujinx.Graphics.Shader.Translation bool intCoords, bool write, bool accurateType, - bool coherent) + bool coherent, + bool separate) { - var dimensions = type.GetDimensions(); + var dimensions = type == SamplerType.None ? 0 : type.GetDimensions(); var dict = isImage ? _usedImages : _usedTextures; var usageFlags = TextureUsageFlags.None; @@ -290,7 +303,7 @@ namespace Ryujinx.Graphics.Shader.Translation // For array textures, we also want to use type as key, // since we may have texture handles stores in the same buffer, but for textures with different types. var keyType = arrayLength > 1 ? type : SamplerType.None; - var info = new TextureInfo(cbufSlot, handle, arrayLength, keyType, format); + var info = new TextureInfo(cbufSlot, handle, arrayLength, separate, keyType, format); var meta = new TextureMeta() { AccurateType = accurateType, @@ -332,6 +345,10 @@ namespace Ryujinx.Graphics.Shader.Translation ? $"{prefix}_tcb_{handle:X}_{format.ToGlslFormat()}" : $"{prefix}_cb{cbufSlot}_{handle:X}_{format.ToGlslFormat()}"; } + else if (type == SamplerType.None) + { + nameSuffix = cbufSlot < 0 ? $"s_tcb_{handle:X}" : $"s_cb{cbufSlot}_{handle:X}"; + } else { nameSuffix = cbufSlot < 0 ? $"{prefix}_tcb_{handle:X}" : $"{prefix}_cb{cbufSlot}_{handle:X}"; @@ -341,6 +358,7 @@ namespace Ryujinx.Graphics.Shader.Translation isImage ? 3 : 2, binding, arrayLength, + separate, $"{_stagePrefix}_{nameSuffix}", meta.Type, info.Format, @@ -495,6 +513,7 @@ namespace Ryujinx.Graphics.Shader.Translation info.CbufSlot, info.Handle, info.ArrayLength, + info.Separate, meta.UsageFlags)); } @@ -514,6 +533,7 @@ namespace Ryujinx.Graphics.Shader.Translation info.CbufSlot, info.Handle, info.ArrayLength, + info.Separate, meta.UsageFlags)); } } diff --git a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs index 581f4372c4..1065355886 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs @@ -413,7 +413,7 @@ namespace Ryujinx.Graphics.Shader.Translation if (Stage == ShaderStage.Vertex) { int ibBinding = resourceManager.Reservations.IndexBufferTextureBinding; - TextureDefinition indexBuffer = new(2, ibBinding, 1, "ib_data", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None); + TextureDefinition indexBuffer = new(2, ibBinding, "ib_data", SamplerType.TextureBuffer); resourceManager.Properties.AddOrUpdateTexture(indexBuffer); int inputMap = _program.AttributeUsage.UsedInputAttributes; @@ -422,7 +422,7 @@ namespace Ryujinx.Graphics.Shader.Translation { int location = BitOperations.TrailingZeroCount(inputMap); int binding = resourceManager.Reservations.GetVertexBufferTextureBinding(location); - TextureDefinition vaBuffer = new(2, binding, 1, $"vb_data{location}", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None); + TextureDefinition vaBuffer = new(2, binding, $"vb_data{location}", SamplerType.TextureBuffer); resourceManager.Properties.AddOrUpdateTexture(vaBuffer); inputMap &= ~(1 << location); @@ -431,7 +431,7 @@ namespace Ryujinx.Graphics.Shader.Translation else if (Stage == ShaderStage.Geometry) { int trbBinding = resourceManager.Reservations.TopologyRemapBufferTextureBinding; - TextureDefinition remapBuffer = new(2, trbBinding, 1, "trb_data", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None); + TextureDefinition remapBuffer = new(2, trbBinding, "trb_data", SamplerType.TextureBuffer); resourceManager.Properties.AddOrUpdateTexture(remapBuffer); int geometryVbOutputSbBinding = resourceManager.Reservations.GeometryVertexOutputStorageBufferBinding; diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetTemplate.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetTemplate.cs index b9abd8fcd3..117f79bb44 100644 --- a/src/Ryujinx.Graphics.Vulkan/DescriptorSetTemplate.cs +++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetTemplate.cs @@ -43,11 +43,11 @@ namespace Ryujinx.Graphics.Vulkan int binding = segment.Binding; int count = segment.Count; - if (setIndex == PipelineBase.UniformSetIndex) + if (IsBufferType(segment.Type)) { entries[seg] = new DescriptorUpdateTemplateEntry() { - DescriptorType = DescriptorType.UniformBuffer, + DescriptorType = segment.Type.Convert(), DstBinding = (uint)binding, DescriptorCount = (uint)count, Offset = structureOffset, @@ -56,76 +56,31 @@ namespace Ryujinx.Graphics.Vulkan structureOffset += (nuint)(Unsafe.SizeOf() * count); } - else if (setIndex == PipelineBase.StorageSetIndex) + else if (IsBufferTextureType(segment.Type)) { entries[seg] = new DescriptorUpdateTemplateEntry() { - DescriptorType = DescriptorType.StorageBuffer, + DescriptorType = segment.Type.Convert(), DstBinding = (uint)binding, DescriptorCount = (uint)count, Offset = structureOffset, - Stride = (nuint)Unsafe.SizeOf() + Stride = (nuint)Unsafe.SizeOf() }; - structureOffset += (nuint)(Unsafe.SizeOf() * count); + structureOffset += (nuint)(Unsafe.SizeOf() * count); } - else if (setIndex == PipelineBase.TextureSetIndex) + else { - if (segment.Type != ResourceType.BufferTexture) + entries[seg] = new DescriptorUpdateTemplateEntry() { - entries[seg] = new DescriptorUpdateTemplateEntry() - { - DescriptorType = DescriptorType.CombinedImageSampler, - DstBinding = (uint)binding, - DescriptorCount = (uint)count, - Offset = structureOffset, - Stride = (nuint)Unsafe.SizeOf() - }; + DescriptorType = segment.Type.Convert(), + DstBinding = (uint)binding, + DescriptorCount = (uint)count, + Offset = structureOffset, + Stride = (nuint)Unsafe.SizeOf() + }; - structureOffset += (nuint)(Unsafe.SizeOf() * count); - } - else - { - entries[seg] = new DescriptorUpdateTemplateEntry() - { - DescriptorType = DescriptorType.UniformTexelBuffer, - DstBinding = (uint)binding, - DescriptorCount = (uint)count, - Offset = structureOffset, - Stride = (nuint)Unsafe.SizeOf() - }; - - structureOffset += (nuint)(Unsafe.SizeOf() * count); - } - } - else if (setIndex == PipelineBase.ImageSetIndex) - { - if (segment.Type != ResourceType.BufferImage) - { - entries[seg] = new DescriptorUpdateTemplateEntry() - { - DescriptorType = DescriptorType.StorageImage, - DstBinding = (uint)binding, - DescriptorCount = (uint)count, - Offset = structureOffset, - Stride = (nuint)Unsafe.SizeOf() - }; - - structureOffset += (nuint)(Unsafe.SizeOf() * count); - } - else - { - entries[seg] = new DescriptorUpdateTemplateEntry() - { - DescriptorType = DescriptorType.StorageTexelBuffer, - DstBinding = (uint)binding, - DescriptorCount = (uint)count, - Offset = structureOffset, - Stride = (nuint)Unsafe.SizeOf() - }; - - structureOffset += (nuint)(Unsafe.SizeOf() * count); - } + structureOffset += (nuint)(Unsafe.SizeOf() * count); } } @@ -237,6 +192,16 @@ namespace Ryujinx.Graphics.Vulkan Template = result; } + private static bool IsBufferType(ResourceType type) + { + return type == ResourceType.UniformBuffer || type == ResourceType.StorageBuffer; + } + + private static bool IsBufferTextureType(ResourceType type) + { + return type == ResourceType.BufferTexture || type == ResourceType.BufferImage; + } + public unsafe void Dispose() { _gd.Api.DestroyDescriptorUpdateTemplate(_device, Template, null); diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index e75e7f4b4c..b46ba9c461 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -706,6 +706,7 @@ namespace Ryujinx.Graphics.Vulkan supportsCubemapView: !IsAmdGcn, supportsNonConstantTextureOffset: false, supportsScaledVertexFormats: FormatCapabilities.SupportsScaledVertexFormats(), + supportsSeparateSampler: true, supportsShaderBallot: false, supportsShaderBarrierDivergence: Vendor != Vendor.Intel, supportsShaderFloat64: Capabilities.SupportsShaderFloat64, diff --git a/src/Ryujinx.ShaderTools/Program.cs b/src/Ryujinx.ShaderTools/Program.cs index 4252f1b258..d2c6bd59e4 100644 --- a/src/Ryujinx.ShaderTools/Program.cs +++ b/src/Ryujinx.ShaderTools/Program.cs @@ -58,10 +58,20 @@ namespace Ryujinx.ShaderTools return MemoryMarshal.Cast(new ReadOnlySpan(_data)[(int)address..]); } + public int QuerySamplerArrayLengthFromPool() + { + return DefaultArrayLength; + } + public int QueryTextureArrayLengthFromBuffer(int slot) { return DefaultArrayLength; } + + public int QueryTextureArrayLengthFromPool() + { + return DefaultArrayLength; + } } private class Options From 89a274c6a6971268aa606c64e9054596b08e0b48 Mon Sep 17 00:00:00 2001 From: TSRBerry <20988865+TSRBerry@users.noreply.github.com> Date: Sat, 27 Apr 2024 01:36:35 +0200 Subject: [PATCH 64/87] ci: Replace macos-latest label with macos-13 (#6729) Due to a change to the GitHub runner labels a few days ago (see: actions/runner#3256) our build workflows for macOS x64 didn't work anymore. According to https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories the macos-13 label is not using arm64 yet. Until a better solution is offered in the linked issue above, we'll keep using the macos-13 label which hopefully doesn't switch to arm64 soon. --- .github/workflows/build.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9e11302fd0..221c7732e9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,7 +20,7 @@ jobs: - { name: win-x64, os: windows-latest, zip_os_name: win_x64 } - { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 } - { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 } - - { name: osx-x64, os: macOS-latest, zip_os_name: osx_x64 } + - { name: osx-x64, os: macos-13, zip_os_name: osx_x64 } fail-fast: false steps: @@ -41,12 +41,12 @@ jobs: - name: Change config filename run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs shell: bash - if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest' + if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13' - name: Change config filename for macOS run: sed -r -i '' 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs shell: bash - if: github.event_name == 'pull_request' && matrix.platform.os == 'macOS-latest' + if: github.event_name == 'pull_request' && matrix.platform.os == 'macos-13' - name: Build run: dotnet build -c "${{ matrix.configuration }}" -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER @@ -61,15 +61,15 @@ jobs: - name: Publish Ryujinx run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained true - if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest' + if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13' - name: Publish Ryujinx.Headless.SDL2 run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true - if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest' + if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13' - name: Publish Ryujinx.Gtk3 run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_gtk -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Gtk3 --self-contained true - if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest' + if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13' - name: Set executable bit run: | @@ -83,21 +83,21 @@ jobs: with: name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }} path: publish - if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest' + if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13' - name: Upload Ryujinx.Headless.SDL2 artifact uses: actions/upload-artifact@v4 with: name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }} path: publish_sdl2_headless - if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest' + if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13' - name: Upload Ryujinx.Gtk3 artifact uses: actions/upload-artifact@v4 with: name: gtk-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }} path: publish_gtk - if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest' + if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13' build_macos: name: macOS Universal (${{ matrix.configuration }}) From 3d4dea624da2cac015b9d86eb8c2360ab7e8df58 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Sun, 28 Apr 2024 17:55:37 +0100 Subject: [PATCH 65/87] HID: Correct direct mouse deltas (#6736) The delta position of the mouse should be the difference between the current and last position. Subtracting the last deltas doesn't really make sense. Won't implement pointer lock for first person games, but might stop some super weird behaviour with the mouse values appearing totally random. --- src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/MouseDevice.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/MouseDevice.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/MouseDevice.cs index b2dd3feaf0..2e62d206b5 100644 --- a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/MouseDevice.cs +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/MouseDevice.cs @@ -23,8 +23,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid newState.Buttons = (MouseButton)buttons; newState.X = mouseX; newState.Y = mouseY; - newState.DeltaX = mouseX - previousEntry.DeltaX; - newState.DeltaY = mouseY - previousEntry.DeltaY; + newState.DeltaX = mouseX - previousEntry.X; + newState.DeltaY = mouseY - previousEntry.Y; newState.WheelDeltaX = scrollX; newState.WheelDeltaY = scrollY; newState.Attributes = connected ? MouseAttribute.IsConnected : MouseAttribute.None; From 5976a5161b850e4082d6f354a1be6b153547590a Mon Sep 17 00:00:00 2001 From: MaxLastBreath <136052075+MaxLastBreath@users.noreply.github.com> Date: Sun, 28 Apr 2024 20:02:29 +0300 Subject: [PATCH 66/87] Fix direct keyboard not working when using a Controller. (#6716) * Fix direct keyboard not working when connected with a controller - Pass KeyboardDriver to NpadController.GetHLEKeyboardInput(). - Always fetch all keyboards if Direct Keyboard is turned on. - Remove unnecessary return null. * Get Keyboard Inputs outside of the controller loop. - Moved GetHLEKeyboardInput outside of the controller loop. - Made GetHLEKeyboardInput public static from public * Removed extra newline * Update src/Ryujinx.Input/HLE/NpadManager.cs Co-authored-by: gdkchan * Update src/Ryujinx.Input/HLE/NpadController.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> --------- Co-authored-by: gdkchan Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> --- src/Ryujinx.Input/HLE/NpadController.cs | 47 ++++++++++++------------- src/Ryujinx.Input/HLE/NpadManager.cs | 10 +++--- 2 files changed, 27 insertions(+), 30 deletions(-) diff --git a/src/Ryujinx.Input/HLE/NpadController.cs b/src/Ryujinx.Input/HLE/NpadController.cs index cde20f5d07..3807452838 100644 --- a/src/Ryujinx.Input/HLE/NpadController.cs +++ b/src/Ryujinx.Input/HLE/NpadController.cs @@ -487,38 +487,35 @@ namespace Ryujinx.Input.HLE return value; } - public KeyboardInput? GetHLEKeyboardInput() + public static KeyboardInput GetHLEKeyboardInput(IGamepadDriver KeyboardDriver) { - if (_gamepad is IKeyboard keyboard) + var keyboard = KeyboardDriver.GetGamepad("0") as IKeyboard; + + KeyboardStateSnapshot keyboardState = keyboard.GetKeyboardStateSnapshot(); + + KeyboardInput hidKeyboard = new() { - KeyboardStateSnapshot keyboardState = keyboard.GetKeyboardStateSnapshot(); + Modifier = 0, + Keys = new ulong[0x4], + }; - KeyboardInput hidKeyboard = new() - { - Modifier = 0, - Keys = new ulong[0x4], - }; + foreach (HLEKeyboardMappingEntry entry in _keyMapping) + { + ulong value = keyboardState.IsPressed(entry.TargetKey) ? 1UL : 0UL; - foreach (HLEKeyboardMappingEntry entry in _keyMapping) - { - ulong value = keyboardState.IsPressed(entry.TargetKey) ? 1UL : 0UL; - - hidKeyboard.Keys[entry.Target / 0x40] |= (value << (entry.Target % 0x40)); - } - - foreach (HLEKeyboardMappingEntry entry in _keyModifierMapping) - { - int value = keyboardState.IsPressed(entry.TargetKey) ? 1 : 0; - - hidKeyboard.Modifier |= value << entry.Target; - } - - return hidKeyboard; + hidKeyboard.Keys[entry.Target / 0x40] |= (value << (entry.Target % 0x40)); } - return null; - } + foreach (HLEKeyboardMappingEntry entry in _keyModifierMapping) + { + int value = keyboardState.IsPressed(entry.TargetKey) ? 1 : 0; + hidKeyboard.Modifier |= value << entry.Target; + } + + return hidKeyboard; + + } protected virtual void Dispose(bool disposing) { diff --git a/src/Ryujinx.Input/HLE/NpadManager.cs b/src/Ryujinx.Input/HLE/NpadManager.cs index 5ae73bda19..4c7bb8b7a4 100644 --- a/src/Ryujinx.Input/HLE/NpadManager.cs +++ b/src/Ryujinx.Input/HLE/NpadManager.cs @@ -231,11 +231,6 @@ namespace Ryujinx.Input.HLE var altMotionState = isJoyconPair ? controller.GetHLEMotionState(true) : default; motionState = (controller.GetHLEMotionState(), altMotionState); - - if (_enableKeyboard) - { - hleKeyboardInput = controller.GetHLEKeyboardInput(); - } } else { @@ -257,6 +252,11 @@ namespace Ryujinx.Input.HLE } } + if (!_blockInputUpdates && _enableKeyboard) + { + hleKeyboardInput = NpadController.GetHLEKeyboardInput(_keyboardDriver); + } + _device.Hid.Npads.Update(hleInputStates); _device.Hid.Npads.UpdateSixAxis(hleMotionStates); From 56c5dbe5572190df3cb176955c50afe9724c2f56 Mon Sep 17 00:00:00 2001 From: Exhigh Date: Mon, 29 Apr 2024 04:51:08 +0530 Subject: [PATCH 67/87] Fix Cursor States On Windows (#6725) * [Ava]: Fix Cursor States On Windows It's been sometime since the last PR #5415 was made and last time i was waiting for Ava 11 to be merged before re-writing the code and along the way forgot about the PR. Anyway this PR supersedes both #5288 and #5415, and fixes issue: #5136 * Now, the bounds for which the cursor should be detected in renderer should be accurate to any scaling / resolution, taking into account the status and the menu bar. ( This issue was partially resolved by #6450 ) * Reduced the number of times the cursor updates from per frame update to updating only when the cursor state needs to be changed. * Fixed the issue wherein you weren't able to resize the window, because of the cursor passthrough which caused the cursor to reset from the reset icon or flicker. * Fixed the issue caused by #6450 which caused the cursor to disappear over the submenus while cursor was set to always hide. * Changed the cursor state to not disappear while the game is being loaded. ( Needs Feedback ). * Removed an unused library import. * PR feedback * Fix excessive line breaks and whitespaces and other feedback * Add a check before calculating cursor idle time, such that it calculates only while the cursor mode is OnIdle. * PR Feedback * Rework the cursor state check code block Co-Authored-By: gdkchan <5624669+gdkchan@users.noreply.github.com> * PR Feedback * A simpler version of the previous implementation. Co-Authored-By: gdkchan <5624669+gdkchan@users.noreply.github.com> * PR Feedback * PR Feedback --------- Co-authored-by: gdkchan <5624669+gdkchan@users.noreply.github.com> --- src/Ryujinx/AppHost.cs | 116 +++++++++++++----- src/Ryujinx/UI/Helpers/Win32NativeInterop.cs | 3 - src/Ryujinx/UI/Renderer/EmbeddedWindow.cs | 2 +- .../UI/ViewModels/MainWindowViewModel.cs | 12 ++ .../UI/Views/Main/MainMenuBarView.axaml | 5 +- src/Ryujinx/UI/Windows/MainWindow.axaml.cs | 7 +- 6 files changed, 108 insertions(+), 37 deletions(-) diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs index 43e7a79ebb..d405f32050 100644 --- a/src/Ryujinx/AppHost.cs +++ b/src/Ryujinx/AppHost.cs @@ -94,6 +94,17 @@ namespace Ryujinx.Ava private long _lastCursorMoveTime; private bool _isCursorInRenderer = true; + private bool _ignoreCursorState = false; + + private enum CursorStates + { + CursorIsHidden, + CursorIsVisible, + ForceChangeCursor + }; + + private CursorStates _cursorState = !ConfigurationState.Instance.Hid.EnableMouse.Value ? + CursorStates.CursorIsVisible : CursorStates.CursorIsHidden; private bool _isStopped; private bool _isActive; @@ -201,23 +212,65 @@ namespace Ryujinx.Ava private void TopLevel_PointerEnteredOrMoved(object sender, PointerEventArgs e) { + if (!_viewModel.IsActive) + { + _isCursorInRenderer = false; + _ignoreCursorState = false; + return; + } + if (sender is MainWindow window) { - _lastCursorMoveTime = Stopwatch.GetTimestamp(); + if (ConfigurationState.Instance.HideCursor.Value == HideCursorMode.OnIdle) + { + _lastCursorMoveTime = Stopwatch.GetTimestamp(); + } var point = e.GetCurrentPoint(window).Position; var bounds = RendererHost.EmbeddedWindow.Bounds; + var windowYOffset = bounds.Y + window.MenuBarHeight; + var windowYLimit = (int)window.Bounds.Height - window.StatusBarHeight - 1; + + if (!_viewModel.ShowMenuAndStatusBar) + { + windowYOffset -= window.MenuBarHeight; + windowYLimit += window.StatusBarHeight + 1; + } _isCursorInRenderer = point.X >= bounds.X && - point.X <= bounds.Width + bounds.X && - point.Y >= bounds.Y && - point.Y <= bounds.Height + bounds.Y; + Math.Ceiling(point.X) <= (int)window.Bounds.Width && + point.Y >= windowYOffset && + point.Y <= windowYLimit && + !_viewModel.IsSubMenuOpen; + + _ignoreCursorState = false; } } private void TopLevel_PointerExited(object sender, PointerEventArgs e) { _isCursorInRenderer = false; + + if (sender is MainWindow window) + { + var point = e.GetCurrentPoint(window).Position; + var bounds = RendererHost.EmbeddedWindow.Bounds; + var windowYOffset = bounds.Y + window.MenuBarHeight; + var windowYLimit = (int)window.Bounds.Height - window.StatusBarHeight - 1; + + if (!_viewModel.ShowMenuAndStatusBar) + { + windowYOffset -= window.MenuBarHeight; + windowYLimit += window.StatusBarHeight + 1; + } + + _ignoreCursorState = (point.X == bounds.X || + Math.Ceiling(point.X) == (int)window.Bounds.Width) && + point.Y >= windowYOffset && + point.Y <= windowYLimit; + } + + _cursorState = CursorStates.ForceChangeCursor; } private void UpdateScalingFilterLevel(object sender, ReactiveEventArgs e) @@ -245,9 +298,14 @@ namespace Ryujinx.Ava if (OperatingSystem.IsWindows()) { - SetCursor(_defaultCursorWin); + if (_cursorState != CursorStates.CursorIsHidden && !_ignoreCursorState) + { + SetCursor(_defaultCursorWin); + } } }); + + _cursorState = CursorStates.CursorIsVisible; } private void HideCursor() @@ -261,6 +319,8 @@ namespace Ryujinx.Ava SetCursor(_invisibleCursorWin); } }); + + _cursorState = CursorStates.CursorIsHidden; } private void SetRendererWindowSize(Size size) @@ -523,6 +583,8 @@ namespace Ryujinx.Ava { _lastCursorMoveTime = Stopwatch.GetTimestamp(); } + + _cursorState = CursorStates.ForceChangeCursor; } public async Task LoadGuestApplication() @@ -1037,38 +1099,32 @@ namespace Ryujinx.Ava if (_viewModel.IsActive) { - if (_isCursorInRenderer) + bool isCursorVisible = true; + + if (_isCursorInRenderer && !_viewModel.ShowLoadProgress) { - if (ConfigurationState.Instance.Hid.EnableMouse) + if (ConfigurationState.Instance.Hid.EnableMouse.Value) { - HideCursor(); + isCursorVisible = ConfigurationState.Instance.HideCursor.Value == HideCursorMode.Never; } else { - switch (ConfigurationState.Instance.HideCursor.Value) - { - case HideCursorMode.Never: - ShowCursor(); - break; - case HideCursorMode.OnIdle: - if (Stopwatch.GetTimestamp() - _lastCursorMoveTime >= CursorHideIdleTime * Stopwatch.Frequency) - { - HideCursor(); - } - else - { - ShowCursor(); - } - break; - case HideCursorMode.Always: - HideCursor(); - break; - } + isCursorVisible = ConfigurationState.Instance.HideCursor.Value == HideCursorMode.Never || + (ConfigurationState.Instance.HideCursor.Value == HideCursorMode.OnIdle && + Stopwatch.GetTimestamp() - _lastCursorMoveTime < CursorHideIdleTime * Stopwatch.Frequency); } } - else + + if (_cursorState != (isCursorVisible ? CursorStates.CursorIsVisible : CursorStates.CursorIsHidden)) { - ShowCursor(); + if (isCursorVisible) + { + ShowCursor(); + } + else + { + HideCursor(); + } } Dispatcher.UIThread.Post(() => @@ -1154,7 +1210,7 @@ namespace Ryujinx.Ava // Touchscreen. bool hasTouch = false; - if (_viewModel.IsActive && !ConfigurationState.Instance.Hid.EnableMouse) + if (_viewModel.IsActive && !ConfigurationState.Instance.Hid.EnableMouse.Value) { hasTouch = TouchScreenManager.Update(true, (_inputManager.MouseDriver as AvaloniaMouseDriver).IsButtonPressed(MouseButton.Button1), ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); } diff --git a/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs b/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs index fce2d518ac..01478cb3de 100644 --- a/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs +++ b/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs @@ -111,8 +111,5 @@ namespace Ryujinx.Ava.UI.Helpers [LibraryImport("user32.dll", SetLastError = true)] public static partial IntPtr SetWindowLongPtrW(IntPtr hWnd, int nIndex, IntPtr value); - - [LibraryImport("user32.dll", SetLastError = true)] - public static partial IntPtr SetWindowLongW(IntPtr hWnd, int nIndex, int value); } } diff --git a/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs b/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs index 8c5e31fff4..0930e77950 100644 --- a/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs +++ b/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs @@ -157,7 +157,7 @@ namespace Ryujinx.Ava.UI.Renderer lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate), style = ClassStyles.CsOwndc, lpszClassName = Marshal.StringToHGlobalUni(_className), - hCursor = CreateArrowCursor(), + hCursor = CreateArrowCursor() }; RegisterClassEx(ref wndClassEx); diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index 130e708c77..549eebf14d 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -104,6 +104,7 @@ namespace Ryujinx.Ava.UI.ViewModels private double _windowHeight; private bool _isActive; + private bool _isSubMenuOpen; public ApplicationData ListSelectedApplication; public ApplicationData GridSelectedApplication; @@ -317,6 +318,17 @@ namespace Ryujinx.Ava.UI.ViewModels } } + public bool IsSubMenuOpen + { + get => _isSubMenuOpen; + set + { + _isSubMenuOpen = value; + + OnPropertyChanged(); + } + } + public bool ShowAll { get => _showAll; diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml index 30358adabf..ea432f78d3 100644 --- a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml +++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml @@ -1,4 +1,4 @@ - + HorizontalAlignment="Left" + IsOpen="{Binding IsSubMenuOpen, Mode=OneWayToSource}"> diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs index 37e2e71a8c..eebbb81fe7 100644 --- a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs @@ -56,6 +56,9 @@ namespace Ryujinx.Ava.UI.Windows public static bool ShowKeyErrorOnLoad { get; set; } public ApplicationLibrary ApplicationLibrary { get; set; } + public readonly double StatusBarHeight; + public readonly double MenuBarHeight; + public MainWindow() { ViewModel = new MainWindowViewModel(); @@ -74,7 +77,9 @@ namespace Ryujinx.Ava.UI.Windows ViewModel.Title = $"Ryujinx {Program.Version}"; // NOTE: Height of MenuBar and StatusBar is not usable here, since it would still be 0 at this point. - double barHeight = MenuBar.MinHeight + StatusBarView.StatusBar.MinHeight; + StatusBarHeight = StatusBarView.StatusBar.MinHeight; + MenuBarHeight = MenuBar.MinHeight; + double barHeight = MenuBarHeight + StatusBarHeight; Height = ((Height - barHeight) / Program.WindowScaleFactor) + barHeight; Width /= Program.WindowScaleFactor; From 65c035cdf8093b2e303ded1dc7253f994570b115 Mon Sep 17 00:00:00 2001 From: Andrew Glaze Date: Mon, 29 Apr 2024 13:18:27 -0400 Subject: [PATCH 68/87] Fix alt key appearing as control in settings menus (#6742) --- src/Ryujinx/UI/Helpers/KeyValueConverter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx/UI/Helpers/KeyValueConverter.cs b/src/Ryujinx/UI/Helpers/KeyValueConverter.cs index cbcf16ab32..5b0d6ee1c0 100644 --- a/src/Ryujinx/UI/Helpers/KeyValueConverter.cs +++ b/src/Ryujinx/UI/Helpers/KeyValueConverter.cs @@ -19,8 +19,8 @@ namespace Ryujinx.Ava.UI.Helpers { Key.ShiftRight, LocaleKeys.KeyShiftRight }, { Key.ControlLeft, LocaleKeys.KeyControlLeft }, { Key.ControlRight, LocaleKeys.KeyControlRight }, - { Key.AltLeft, LocaleKeys.KeyControlLeft }, - { Key.AltRight, LocaleKeys.KeyControlRight }, + { Key.AltLeft, LocaleKeys.KeyAltLeft }, + { Key.AltRight, LocaleKeys.KeyAltRight }, { Key.WinLeft, LocaleKeys.KeyWinLeft }, { Key.WinRight, LocaleKeys.KeyWinRight }, { Key.Up, LocaleKeys.KeyUp }, From d0cc13ce0b2a149c3d19aa58a2f12ddc6cc0196f Mon Sep 17 00:00:00 2001 From: MutantAura <44103205+MutantAura@users.noreply.github.com> Date: Wed, 1 May 2024 17:21:24 +0100 Subject: [PATCH 69/87] UI: Fix some MainWindow bugs and implement menubar items to change window size. (#6750) * Do not save window dimensions when maximized. * Implement option to disable window size/position memory. * Remove logging statements * Implement menubar items for common window sizes. * formatting fixes * Set 720p window as a composite value. * Remove unused using * Fix exception paramter. * Force restore window when a size change is requested * Fix some resizing bugs. --- .../Configuration/ConfigurationFileFormat.cs | 7 +++- .../Configuration/ConfigurationState.cs | 18 ++++++++++ src/Ryujinx/Assets/Locales/en_US.json | 5 +++ .../UI/ViewModels/SettingsViewModel.cs | 3 ++ .../UI/Views/Main/MainMenuBarView.axaml | 6 ++++ .../UI/Views/Main/MainMenuBarView.axaml.cs | 35 ++++++++++++++++++ .../UI/Views/Settings/SettingsUIView.axaml | 3 ++ src/Ryujinx/UI/Windows/MainWindow.axaml.cs | 36 ++++++++++++++----- 8 files changed, 103 insertions(+), 10 deletions(-) diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs index 3387e1be13..af3ad0a1da 100644 --- a/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs @@ -15,7 +15,7 @@ namespace Ryujinx.UI.Common.Configuration /// /// The current version of the file format /// - public const int CurrentVersion = 50; + public const int CurrentVersion = 51; /// /// Version of the configuration file format @@ -162,6 +162,11 @@ namespace Ryujinx.UI.Common.Configuration /// public bool ShowConfirmExit { get; set; } + /// + /// Enables or disables save window size, position and state on close. + /// + public bool RememberWindowState { get; set; } + /// /// Enables hardware-accelerated rendering for Avalonia /// diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs index 2609dc9baa..01c60a5e23 100644 --- a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs @@ -626,6 +626,11 @@ namespace Ryujinx.UI.Common.Configuration /// public ReactiveObject ShowConfirmExit { get; private set; } + /// + /// Enables or disables save window size, position and state on close. + /// + public ReactiveObject RememberWindowState { get; private set; } + /// /// Enables hardware-accelerated rendering for Avalonia /// @@ -647,6 +652,7 @@ namespace Ryujinx.UI.Common.Configuration EnableDiscordIntegration = new ReactiveObject(); CheckUpdatesOnStart = new ReactiveObject(); ShowConfirmExit = new ReactiveObject(); + RememberWindowState = new ReactiveObject(); EnableHardwareAcceleration = new ReactiveObject(); HideCursor = new ReactiveObject(); } @@ -684,6 +690,7 @@ namespace Ryujinx.UI.Common.Configuration EnableDiscordIntegration = EnableDiscordIntegration, CheckUpdatesOnStart = CheckUpdatesOnStart, ShowConfirmExit = ShowConfirmExit, + RememberWindowState = RememberWindowState, EnableHardwareAcceleration = EnableHardwareAcceleration, HideCursor = HideCursor, EnableVsync = Graphics.EnableVsync, @@ -792,6 +799,7 @@ namespace Ryujinx.UI.Common.Configuration EnableDiscordIntegration.Value = true; CheckUpdatesOnStart.Value = true; ShowConfirmExit.Value = true; + RememberWindowState.Value = true; EnableHardwareAcceleration.Value = true; HideCursor.Value = HideCursorMode.OnIdle; Graphics.EnableVsync.Value = true; @@ -1459,6 +1467,15 @@ namespace Ryujinx.UI.Common.Configuration configurationFileUpdated = true; } + if (configurationFileFormat.Version < 51) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 51."); + + configurationFileFormat.RememberWindowState = true; + + configurationFileUpdated = true; + } + Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog; Graphics.ResScale.Value = configurationFileFormat.ResScale; Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom; @@ -1489,6 +1506,7 @@ namespace Ryujinx.UI.Common.Configuration EnableDiscordIntegration.Value = configurationFileFormat.EnableDiscordIntegration; CheckUpdatesOnStart.Value = configurationFileFormat.CheckUpdatesOnStart; ShowConfirmExit.Value = configurationFileFormat.ShowConfirmExit; + RememberWindowState.Value = configurationFileFormat.RememberWindowState; EnableHardwareAcceleration.Value = configurationFileFormat.EnableHardwareAcceleration; HideCursor.Value = configurationFileFormat.HideCursor; Graphics.EnableVsync.Value = configurationFileFormat.EnableVsync; diff --git a/src/Ryujinx/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json index 77ad7d1f8c..2299d9e665 100644 --- a/src/Ryujinx/Assets/Locales/en_US.json +++ b/src/Ryujinx/Assets/Locales/en_US.json @@ -30,6 +30,10 @@ "MenuBarToolsManageFileTypes": "Manage file types", "MenuBarToolsInstallFileTypes": "Install file types", "MenuBarToolsUninstallFileTypes": "Uninstall file types", + "MenuBarView": "_View", + "MenuBarViewWindow": "Window Size", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", "MenuBarHelp": "_Help", "MenuBarHelpCheckForUpdates": "Check for Updates", "MenuBarHelpAbout": "About", @@ -92,6 +96,7 @@ "SettingsTabGeneralEnableDiscordRichPresence": "Enable Discord Rich Presence", "SettingsTabGeneralCheckUpdatesOnLaunch": "Check for Updates on Launch", "SettingsTabGeneralShowConfirmExitDialog": "Show \"Confirm Exit\" Dialog", + "SettingsTabGeneralRememberWindowState": "Remember Window Size/Position", "SettingsTabGeneralHideCursor": "Hide Cursor:", "SettingsTabGeneralHideCursorNever": "Never", "SettingsTabGeneralHideCursorOnIdle": "On Idle", diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index 6074a5fdb3..0664f436f0 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -131,6 +131,7 @@ namespace Ryujinx.Ava.UI.ViewModels public bool EnableDiscordIntegration { get; set; } public bool CheckUpdatesOnStart { get; set; } public bool ShowConfirmExit { get; set; } + public bool RememberWindowState { get; set; } public int HideCursor { get; set; } public bool EnableDockedMode { get; set; } public bool EnableKeyboard { get; set; } @@ -390,6 +391,7 @@ namespace Ryujinx.Ava.UI.ViewModels EnableDiscordIntegration = config.EnableDiscordIntegration; CheckUpdatesOnStart = config.CheckUpdatesOnStart; ShowConfirmExit = config.ShowConfirmExit; + RememberWindowState = config.RememberWindowState; HideCursor = (int)config.HideCursor.Value; GameDirectories.Clear(); @@ -474,6 +476,7 @@ namespace Ryujinx.Ava.UI.ViewModels config.EnableDiscordIntegration.Value = EnableDiscordIntegration; config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart; config.ShowConfirmExit.Value = ShowConfirmExit; + config.RememberWindowState.Value = RememberWindowState; config.HideCursor.Value = (HideCursorMode)HideCursor; if (_directoryChanged) diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml index ea432f78d3..ac37361106 100644 --- a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml +++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml @@ -186,6 +186,12 @@ + + + + + + + { + ViewModel.WindowState = WindowState.Normal; + + height += (int)Window.StatusBarHeight + (int)Window.MenuBarHeight; + + Window.Arrange(new Rect(Window.Position.X, Window.Position.Y, width, height)); + }); + } + } + public async void CheckForUpdates(object sender, RoutedEventArgs e) { if (Updater.CanUpdate(true)) diff --git a/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml index 6504637e6c..b60058fcbf 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml +++ b/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml @@ -36,6 +36,9 @@ + + + Date: Thu, 2 May 2024 08:33:28 -0300 Subject: [PATCH 70/87] Fix system dateTime loading in avalonia LoadCurrentConfiguration (#6676) * Fix system dateTime loading in avalonia LoadCurrentConfiguration * Rename local var to not use upper camel case --- src/Ryujinx/UI/ViewModels/SettingsViewModel.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index 0664f436f0..0f43d0f7fc 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -412,10 +412,11 @@ namespace Ryujinx.Ava.UI.ViewModels Language = (int)config.System.Language.Value; TimeZone = config.System.TimeZone; - DateTime currentDateTime = DateTime.Now; - + DateTime currentHostDateTime = DateTime.Now; + TimeSpan systemDateTimeOffset = TimeSpan.FromSeconds(config.System.SystemTimeOffset); + DateTime currentDateTime = currentHostDateTime.Add(systemDateTimeOffset); CurrentDate = currentDateTime.Date; - CurrentTime = currentDateTime.TimeOfDay.Add(TimeSpan.FromSeconds(config.System.SystemTimeOffset)); + CurrentTime = currentDateTime.TimeOfDay; EnableVsync = config.Graphics.EnableVsync; EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks; From a23d8cb92f3f1bb8dc144f4d9fb3fddee749feae Mon Sep 17 00:00:00 2001 From: Marco Carvalho Date: Wed, 8 May 2024 08:53:25 -0300 Subject: [PATCH 71/87] Replace "List.ForEach" for "foreach" (#6783) * Replace "List.ForEach" for "foreach" * dotnet format * Update Ptc.cs * Update GpuContext.cs --- src/ARMeilleure/Translation/PTC/Ptc.cs | 10 ++++++++-- src/Ryujinx.Graphics.Gpu/GpuContext.cs | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/ARMeilleure/Translation/PTC/Ptc.cs b/src/ARMeilleure/Translation/PTC/Ptc.cs index 58f065342e..f56bdce1cd 100644 --- a/src/ARMeilleure/Translation/PTC/Ptc.cs +++ b/src/ARMeilleure/Translation/PTC/Ptc.cs @@ -857,8 +857,14 @@ namespace ARMeilleure.Translation.PTC Stopwatch sw = Stopwatch.StartNew(); - threads.ForEach((thread) => thread.Start()); - threads.ForEach((thread) => thread.Join()); + foreach (var thread in threads) + { + thread.Start(); + } + foreach (var thread in threads) + { + thread.Join(); + } threads.Clear(); diff --git a/src/Ryujinx.Graphics.Gpu/GpuContext.cs b/src/Ryujinx.Graphics.Gpu/GpuContext.cs index aa0084fdc9..53ea8cb277 100644 --- a/src/Ryujinx.Graphics.Gpu/GpuContext.cs +++ b/src/Ryujinx.Graphics.Gpu/GpuContext.cs @@ -395,8 +395,14 @@ namespace Ryujinx.Graphics.Gpu { Renderer.CreateSync(SyncNumber, strict); - SyncActions.ForEach(action => action.SyncPreAction(syncpoint)); - SyncpointActions.ForEach(action => action.SyncPreAction(syncpoint)); + foreach (var action in SyncActions) + { + action.SyncPreAction(syncpoint); + } + foreach (var action in SyncpointActions) + { + action.SyncPreAction(syncpoint); + } SyncNumber++; From 1a676ee913ee98917ec384481c8f6666835f942f Mon Sep 17 00:00:00 2001 From: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> Date: Tue, 14 May 2024 09:59:28 -0400 Subject: [PATCH 72/87] Update DotSettings (#6535) --- Ryujinx.sln.DotSettings | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Ryujinx.sln.DotSettings b/Ryujinx.sln.DotSettings index 049bdaf69d..ed7f3e9118 100644 --- a/Ryujinx.sln.DotSettings +++ b/Ryujinx.sln.DotSettings @@ -4,6 +4,8 @@ UseExplicitType UseExplicitType <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="I" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"><ElementKinds><Kind Name="NAMESPACE" /><Kind Name="CLASS" /><Kind Name="STRUCT" /><Kind Name="ENUM" /><Kind Name="DELEGATE" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="I" Suffix="" Style="AaBb" /></Policy></Policy> + True True True True From 6e40b645547f6453fb5e0bee98d2fa6330e747c7 Mon Sep 17 00:00:00 2001 From: TSRBerry <20988865+TSRBerry@users.noreply.github.com> Date: Tue, 14 May 2024 16:06:40 +0200 Subject: [PATCH 73/87] Add linux specific files to local builds (#6762) --- src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj | 2 +- src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj | 2 +- src/Ryujinx/Ryujinx.csproj | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj b/src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj index 68bf98981e..b4453f9d79 100644 --- a/src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj +++ b/src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj @@ -63,7 +63,7 @@ - + Always diff --git a/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj b/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj index cc5a365183..6102295444 100644 --- a/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj +++ b/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj @@ -48,7 +48,7 @@ - + Always diff --git a/src/Ryujinx/Ryujinx.csproj b/src/Ryujinx/Ryujinx.csproj index 38453f2c66..a43f50063f 100644 --- a/src/Ryujinx/Ryujinx.csproj +++ b/src/Ryujinx/Ryujinx.csproj @@ -70,7 +70,8 @@ - + @@ -88,7 +89,7 @@ - + Always From e9edf0ab7fa9c64347c3e53e0d9d862756b1627f Mon Sep 17 00:00:00 2001 From: Luke Warner <65521430+LukeWarnut@users.noreply.github.com> Date: Tue, 14 May 2024 10:19:43 -0400 Subject: [PATCH 74/87] Update outdated Windows version warning (#6481) * Change text * clarify version number * update gtk --- src/Ryujinx.Gtk3/Program.cs | 2 +- src/Ryujinx/Program.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx.Gtk3/Program.cs b/src/Ryujinx.Gtk3/Program.cs index 1845c512e4..f699343135 100644 --- a/src/Ryujinx.Gtk3/Program.cs +++ b/src/Ryujinx.Gtk3/Program.cs @@ -75,7 +75,7 @@ namespace Ryujinx if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134)) { - MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MbIconWarning); + MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nRyujinx supports Windows 10 version 1803 and newer.\n", $"Ryujinx {Version}", MbIconWarning); } // Parse arguments diff --git a/src/Ryujinx/Program.cs b/src/Ryujinx/Program.cs index 89e895e81b..f925ce154f 100644 --- a/src/Ryujinx/Program.cs +++ b/src/Ryujinx/Program.cs @@ -40,7 +40,7 @@ namespace Ryujinx.Ava if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134)) { - _ = MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MbIconwarning); + _ = MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nRyujinx supports Windows 10 version 1803 and newer.\n", $"Ryujinx {Version}", MbIconwarning); } PreviewerDetached = true; From cada4d04efe8a3c19c290da3257c231da9872e40 Mon Sep 17 00:00:00 2001 From: Tsubasa0504 <60139445+Tsubasa0504@users.noreply.github.com> Date: Tue, 14 May 2024 23:26:49 +0900 Subject: [PATCH 75/87] HID: Stub IHidServer: 134 (SetNpadAnalogStickUseCenterClamp) (#6664) * Add files via upload * Update IHidServer.cs mistakes... * format how do i do it * Update src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs Co-authored-by: Agatem * Update src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs Co-authored-by: Agatem * bruh * Apply suggestions from code review Co-authored-by: gdkchan * use readuint32 instead * second thought * i hope it works thanks someone higher up with the same thing * pid * Apply suggestions from code review Co-authored-by: Ac_K * styles i think * Apply suggestions from code review Co-authored-by: makigumo --------- Co-authored-by: Agatem Co-authored-by: gdkchan Co-authored-by: Ac_K Co-authored-by: makigumo --- src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs b/src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs index bcc87f53bf..e3f505f371 100644 --- a/src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs +++ b/src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs @@ -22,6 +22,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid private bool _sixAxisSensorFusionEnabled; private bool _unintendedHomeButtonInputProtectionEnabled; + private bool _npadAnalogStickCenterClampEnabled; private bool _vibrationPermitted; private bool _usbFullKeyControllerEnabled; private readonly bool _isFirmwareUpdateAvailableForSixAxisSensor; @@ -1107,6 +1108,19 @@ namespace Ryujinx.HLE.HOS.Services.Hid // If not, it returns nothing. } + [CommandCmif(134)] // 6.1.0+ + // SetNpadUseAnalogStickUseCenterClamp(bool Enable, nn::applet::AppletResourceUserId) + public ResultCode SetNpadUseAnalogStickUseCenterClamp(ServiceCtx context) + { + ulong pid = context.RequestData.ReadUInt64(); + _npadAnalogStickCenterClampEnabled = context.RequestData.ReadUInt32() != 0; + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { pid, appletResourceUserId, _npadAnalogStickCenterClampEnabled }); + + return ResultCode.Success; + } + [CommandCmif(200)] // GetVibrationDeviceInfo(nn::hid::VibrationDeviceHandle) -> nn::hid::VibrationDeviceInfo public ResultCode GetVibrationDeviceInfo(ServiceCtx context) From 44dbab3848c8831d27e50f7252d759a2494ad556 Mon Sep 17 00:00:00 2001 From: MutantAura <44103205+MutantAura@users.noreply.github.com> Date: Tue, 14 May 2024 15:36:44 +0100 Subject: [PATCH 76/87] discordRPC: Truncate game title and details if they exceed discord byte limit. (#6581) * Truncate game title and details if they exceed DiscordRPC limit. * Update implementation to a byte total check. * Track if the string has actually been modified correctly. * Allow an early function return and simplify logic. * Better handling of large input strings and minimise loop opportunities. * Remove unused using. * Update to `applicationName` over `titleName`. --- .../DiscordIntegrationModule.cs | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs b/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs index bbece1e1d5..fb07195d08 100644 --- a/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs +++ b/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs @@ -1,6 +1,7 @@ using DiscordRPC; using Ryujinx.Common; using Ryujinx.UI.Common.Configuration; +using System.Text; namespace Ryujinx.UI.Common { @@ -9,6 +10,9 @@ namespace Ryujinx.UI.Common private const string Description = "A simple, experimental Nintendo Switch emulator."; private const string ApplicationId = "1216775165866807456"; + private const int ApplicationByteLimit = 128; + private const string Ellipsis = "…"; + private static DiscordRpcClient _discordClient; private static RichPresence _discordPresenceMain; @@ -60,18 +64,18 @@ namespace Ryujinx.UI.Common } } - public static void SwitchToPlayingState(string titleId, string titleName) + public static void SwitchToPlayingState(string titleId, string applicationName) { _discordClient?.SetPresence(new RichPresence { Assets = new Assets { LargeImageKey = "game", - LargeImageText = titleName, + LargeImageText = TruncateToByteLength(applicationName, ApplicationByteLimit), SmallImageKey = "ryujinx", SmallImageText = Description, }, - Details = $"Playing {titleName}", + Details = TruncateToByteLength($"Playing {applicationName}", ApplicationByteLimit), State = (titleId == "0000000000000000") ? "Homebrew" : titleId.ToUpper(), Timestamps = Timestamps.Now, Buttons = @@ -90,6 +94,28 @@ namespace Ryujinx.UI.Common _discordClient?.SetPresence(_discordPresenceMain); } + private static string TruncateToByteLength(string input, int byteLimit) + { + if (Encoding.UTF8.GetByteCount(input) <= byteLimit) + { + return input; + } + + // Find the length to trim the string to guarantee we have space for the trailing ellipsis. + int trimLimit = byteLimit - Encoding.UTF8.GetByteCount(Ellipsis); + + // Basic trim to best case scenario of 1 byte characters. + input = input[..trimLimit]; + + while (Encoding.UTF8.GetByteCount(input) > trimLimit) + { + // Remove one character from the end of the string at a time. + input = input[..^1]; + } + + return input.TrimEnd() + Ellipsis; + } + public static void Exit() { _discordClient?.Dispose(); From 3a3b51893ee272af49d762387da5b27743786d56 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Tue, 14 May 2024 11:47:16 -0300 Subject: [PATCH 77/87] Add support for bindless textures from storage buffer on Vulkan (#6721) * Halve primitive ID when converting quads to triangles * Shader cache version bump * Add support for bindless textures from storage buffer on Vulkan --- src/Ryujinx.Graphics.GAL/Capabilities.cs | 3 +++ .../Shader/DiskCache/DiskCacheGpuAccessor.cs | 14 +++++++++++-- .../Shader/DiskCache/DiskCacheHostStorage.cs | 2 +- .../DiskCache/ParallelDiskCacheLoader.cs | 8 +++++--- .../Shader/GpuAccessor.cs | 20 ++++++++++++++++--- .../Shader/GpuChannelGraphicsState.cs | 11 ++++++++-- .../Shader/ShaderCache.cs | 2 +- src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs | 3 ++- .../GpuGraphicsState.cs | 10 +++++++++- src/Ryujinx.Graphics.Shader/IGpuAccessor.cs | 1 + .../Instructions/InstEmitAttribute.cs | 10 ++++++++++ .../Optimizations/BindlessElimination.cs | 4 ++-- .../Translation/ShaderDefinitions.cs | 2 ++ src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs | 3 ++- 14 files changed, 76 insertions(+), 17 deletions(-) diff --git a/src/Ryujinx.Graphics.GAL/Capabilities.cs b/src/Ryujinx.Graphics.GAL/Capabilities.cs index 70736fbd61..779ce5b5dc 100644 --- a/src/Ryujinx.Graphics.GAL/Capabilities.cs +++ b/src/Ryujinx.Graphics.GAL/Capabilities.cs @@ -36,6 +36,7 @@ namespace Ryujinx.Graphics.GAL public readonly bool SupportsMismatchingViewFormat; public readonly bool SupportsCubemapView; public readonly bool SupportsNonConstantTextureOffset; + public readonly bool SupportsQuads; public readonly bool SupportsSeparateSampler; public readonly bool SupportsShaderBallot; public readonly bool SupportsShaderBarrierDivergence; @@ -93,6 +94,7 @@ namespace Ryujinx.Graphics.GAL bool supportsMismatchingViewFormat, bool supportsCubemapView, bool supportsNonConstantTextureOffset, + bool supportsQuads, bool supportsSeparateSampler, bool supportsShaderBallot, bool supportsShaderBarrierDivergence, @@ -146,6 +148,7 @@ namespace Ryujinx.Graphics.GAL SupportsMismatchingViewFormat = supportsMismatchingViewFormat; SupportsCubemapView = supportsCubemapView; SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset; + SupportsQuads = supportsQuads; SupportsSeparateSampler = supportsSeparateSampler; SupportsShaderBallot = supportsShaderBallot; SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs index 45f32e2d39..3c7664b77a 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs @@ -18,6 +18,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache private readonly ShaderSpecializationState _newSpecState; private readonly int _stageIndex; private readonly bool _isVulkan; + private readonly bool _hasGeometryShader; + private readonly bool _supportsQuads; /// /// Creates a new instance of the cached GPU state accessor for shader translation. @@ -29,6 +31,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache /// Shader specialization state of the recompiled shader /// Resource counts shared across all shader stages /// Shader stage index + /// Indicates if a geometry shader is present public DiskCacheGpuAccessor( GpuContext context, ReadOnlyMemory data, @@ -36,7 +39,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache ShaderSpecializationState oldSpecState, ShaderSpecializationState newSpecState, ResourceCounts counts, - int stageIndex) : base(context, counts, stageIndex) + int stageIndex, + bool hasGeometryShader) : base(context, counts, stageIndex) { _data = data; _cb1Data = cb1Data; @@ -44,6 +48,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache _newSpecState = newSpecState; _stageIndex = stageIndex; _isVulkan = context.Capabilities.Api == TargetApi.Vulkan; + _hasGeometryShader = hasGeometryShader; + _supportsQuads = context.Capabilities.SupportsQuads; if (stageIndex == (int)ShaderStage.Geometry - 1) { @@ -100,7 +106,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache /// public GpuGraphicsState QueryGraphicsState() { - return _oldSpecState.GraphicsState.CreateShaderGraphicsState(!_isVulkan, _isVulkan || _oldSpecState.GraphicsState.YNegateEnabled); + return _oldSpecState.GraphicsState.CreateShaderGraphicsState( + !_isVulkan, + _supportsQuads, + _hasGeometryShader, + _isVulkan || _oldSpecState.GraphicsState.YNegateEnabled); } /// diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs index 2c19cc4b98..ea54049c2a 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs @@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMinor = 2; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; - private const uint CodeGenVersion = 6577; + private const uint CodeGenVersion = 5936; private const string SharedTocFileName = "shared.toc"; private const string SharedDataFileName = "shared.data"; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs index 153fc44276..20f96462ea 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs @@ -601,6 +601,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache TargetApi api = _context.Capabilities.Api; + bool hasCachedGs = guestShaders[4].HasValue; + for (int stageIndex = Constants.ShaderStages - 1; stageIndex >= 0; stageIndex--) { if (guestShaders[stageIndex + 1].HasValue) @@ -610,7 +612,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache byte[] guestCode = shader.Code; byte[] cb1Data = shader.Cb1Data; - DiskCacheGpuAccessor gpuAccessor = new(_context, guestCode, cb1Data, specState, newSpecState, counts, stageIndex); + DiskCacheGpuAccessor gpuAccessor = new(_context, guestCode, cb1Data, specState, newSpecState, counts, stageIndex, hasCachedGs); TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags, 0); if (nextStage != null) @@ -623,7 +625,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache byte[] guestCodeA = guestShaders[0].Value.Code; byte[] cb1DataA = guestShaders[0].Value.Cb1Data; - DiskCacheGpuAccessor gpuAccessorA = new(_context, guestCodeA, cb1DataA, specState, newSpecState, counts, 0); + DiskCacheGpuAccessor gpuAccessorA = new(_context, guestCodeA, cb1DataA, specState, newSpecState, counts, 0, hasCachedGs); translatorContexts[0] = DecodeGraphicsShader(gpuAccessorA, api, DefaultFlags | TranslationFlags.VertexA, 0); } @@ -711,7 +713,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache GuestCodeAndCbData shader = guestShaders[0].Value; ResourceCounts counts = new(); ShaderSpecializationState newSpecState = new(ref specState.ComputeState); - DiskCacheGpuAccessor gpuAccessor = new(_context, shader.Code, shader.Cb1Data, specState, newSpecState, counts, 0); + DiskCacheGpuAccessor gpuAccessor = new(_context, shader.Code, shader.Cb1Data, specState, newSpecState, counts, 0, false); gpuAccessor.InitializeReservedCounts(tfEnabled: false, vertexAsCompute: false); TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, 0); diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs index 04949690a2..1be75f242b 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs @@ -17,6 +17,8 @@ namespace Ryujinx.Graphics.Gpu.Shader private readonly int _stageIndex; private readonly bool _compute; private readonly bool _isVulkan; + private readonly bool _hasGeometryShader; + private readonly bool _supportsQuads; /// /// Creates a new instance of the GPU state accessor for graphics shader translation. @@ -25,12 +27,20 @@ namespace Ryujinx.Graphics.Gpu.Shader /// GPU channel /// Current GPU state /// Graphics shader stage index (0 = Vertex, 4 = Fragment) - public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state, int stageIndex) : base(context, state.ResourceCounts, stageIndex) + /// Indicates if a geometry shader is present + public GpuAccessor( + GpuContext context, + GpuChannel channel, + GpuAccessorState state, + int stageIndex, + bool hasGeometryShader) : base(context, state.ResourceCounts, stageIndex) { - _isVulkan = context.Capabilities.Api == TargetApi.Vulkan; _channel = channel; _state = state; _stageIndex = stageIndex; + _isVulkan = context.Capabilities.Api == TargetApi.Vulkan; + _hasGeometryShader = hasGeometryShader; + _supportsQuads = context.Capabilities.SupportsQuads; if (stageIndex == (int)ShaderStage.Geometry - 1) { @@ -105,7 +115,11 @@ namespace Ryujinx.Graphics.Gpu.Shader /// public GpuGraphicsState QueryGraphicsState() { - return _state.GraphicsState.CreateShaderGraphicsState(!_isVulkan, _isVulkan || _state.GraphicsState.YNegateEnabled); + return _state.GraphicsState.CreateShaderGraphicsState( + !_isVulkan, + _supportsQuads, + _hasGeometryShader, + _isVulkan || _state.GraphicsState.YNegateEnabled); } /// diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs index b5bc4df3c0..765bef7d4a 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs @@ -106,8 +106,11 @@ namespace Ryujinx.Graphics.Gpu.Shader /// Creates a new graphics state from this state that can be used for shader generation. /// /// Indicates if the host API supports alpha test operations + /// Indicates if the host API supports quad primitives + /// Indicates if a geometry shader is used + /// If true, indicates that the fragment origin is the upper left corner of the viewport, otherwise it is the lower left corner /// GPU graphics state that can be used for shader translation - public readonly GpuGraphicsState CreateShaderGraphicsState(bool hostSupportsAlphaTest, bool originUpperLeft) + public readonly GpuGraphicsState CreateShaderGraphicsState(bool hostSupportsAlphaTest, bool hostSupportsQuads, bool hasGeometryShader, bool originUpperLeft) { AlphaTestOp alphaTestOp; @@ -130,6 +133,9 @@ namespace Ryujinx.Graphics.Gpu.Shader }; } + bool isQuad = Topology == PrimitiveTopology.Quads || Topology == PrimitiveTopology.QuadStrip; + bool halvePrimitiveId = !hostSupportsQuads && !hasGeometryShader && isQuad; + return new GpuGraphicsState( EarlyZForce, ConvertToInputTopology(Topology, TessellationMode), @@ -149,7 +155,8 @@ namespace Ryujinx.Graphics.Gpu.Shader in FragmentOutputTypes, DualSourceBlendEnable, YNegateEnabled, - originUpperLeft); + originUpperLeft, + halvePrimitiveId); } /// diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs index 31cc94a25c..4fc66c4c06 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs @@ -339,7 +339,7 @@ namespace Ryujinx.Graphics.Gpu.Shader if (gpuVa != 0) { - GpuAccessor gpuAccessor = new(_context, channel, gpuAccessorState, stageIndex); + GpuAccessor gpuAccessor = new(_context, channel, gpuAccessorState, stageIndex, addresses.Geometry != 0); TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags, gpuVa); if (nextStage != null) diff --git a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs index d56c40af46..2a39ae446f 100644 --- a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs +++ b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs @@ -161,6 +161,7 @@ namespace Ryujinx.Graphics.OpenGL supportsBgraFormat: false, supportsR4G4Format: false, supportsR4G4B4A4Format: true, + supportsScaledVertexFormats: true, supportsSnormBufferTextureFormat: false, supports5BitComponentFormat: true, supportsSparseBuffer: false, @@ -175,7 +176,7 @@ namespace Ryujinx.Graphics.OpenGL supportsMismatchingViewFormat: HwCapabilities.SupportsMismatchingViewFormat, supportsCubemapView: true, supportsNonConstantTextureOffset: HwCapabilities.SupportsNonConstantTextureOffset, - supportsScaledVertexFormats: true, + supportsQuads: HwCapabilities.SupportsQuads, supportsSeparateSampler: false, supportsShaderBallot: HwCapabilities.SupportsShaderBallot, supportsShaderBarrierDivergence: !(intelWindows || intelUnix), diff --git a/src/Ryujinx.Graphics.Shader/GpuGraphicsState.cs b/src/Ryujinx.Graphics.Shader/GpuGraphicsState.cs index f16c71d559..38684002c2 100644 --- a/src/Ryujinx.Graphics.Shader/GpuGraphicsState.cs +++ b/src/Ryujinx.Graphics.Shader/GpuGraphicsState.cs @@ -102,6 +102,11 @@ namespace Ryujinx.Graphics.Shader /// public readonly bool OriginUpperLeft; + /// + /// Indicates that the primitive ID values on the shader should be halved due to quad to triangles conversion. + /// + public readonly bool HalvePrimitiveId; + /// /// Creates a new GPU graphics state. /// @@ -124,6 +129,7 @@ namespace Ryujinx.Graphics.Shader /// Indicates whether dual source blend is enabled /// Indicates if negation of the viewport Y axis is enabled /// If true, indicates that the fragment origin is the upper left corner of the viewport, otherwise it is the lower left corner + /// Indicates that the primitive ID values on the shader should be halved due to quad to triangles conversion public GpuGraphicsState( bool earlyZForce, InputTopology topology, @@ -143,7 +149,8 @@ namespace Ryujinx.Graphics.Shader in Array8 fragmentOutputTypes, bool dualSourceBlendEnable, bool yNegateEnabled, - bool originUpperLeft) + bool originUpperLeft, + bool halvePrimitiveId) { EarlyZForce = earlyZForce; Topology = topology; @@ -164,6 +171,7 @@ namespace Ryujinx.Graphics.Shader DualSourceBlendEnable = dualSourceBlendEnable; YNegateEnabled = yNegateEnabled; OriginUpperLeft = originUpperLeft; + HalvePrimitiveId = halvePrimitiveId; } } } diff --git a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs index b1a9f9f842..3dc4ad907b 100644 --- a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs +++ b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs @@ -135,6 +135,7 @@ namespace Ryujinx.Graphics.Shader default, false, false, + false, false); } diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs index 63ce38e251..c704156bcb 100644 --- a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs @@ -84,6 +84,10 @@ namespace Ryujinx.Graphics.Shader.Instructions value = context.IConvertU32ToFP32(value); } } + else if (offset == AttributeConsts.PrimitiveId && context.TranslatorContext.Definitions.HalvePrimitiveId) + { + value = context.ShiftRightS32(value, Const(1)); + } context.Copy(Register(rd), value); } @@ -187,6 +191,12 @@ namespace Ryujinx.Graphics.Shader.Instructions } } } + else if (op.Imm10 == AttributeConsts.PrimitiveId && context.TranslatorContext.Definitions.HalvePrimitiveId) + { + // If quads are used, but the host does not support them, they need to be converted to triangles. + // Since each quad becomes 2 triangles, we need to compensate here and divide primitive ID by 2. + res = context.ShiftRightS32(res, Const(1)); + } else if (op.Imm10 == AttributeConsts.FrontFacing && context.TranslatorContext.GpuAccessor.QueryHostHasFrontFacingBug()) { // gl_FrontFacing sometimes has incorrect (flipped) values depending how it is accessed on Intel GPUs. diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs index 223215439f..4128af241b 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs @@ -66,9 +66,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations if (nvHandle.AsgOp is not Operation handleOp || handleOp.Inst != Instruction.Load || - handleOp.StorageKind != StorageKind.Input) + (handleOp.StorageKind != StorageKind.Input && handleOp.StorageKind != StorageKind.StorageBuffer)) { - // Right now, we only allow bindless access when the handle comes from a shader input. + // Right now, we only allow bindless access when the handle comes from a shader input or storage buffer. // This is an artificial limitation to prevent it from being used in cases where it // would have a large performance impact of loading all textures in the pool. // It might be removed in the future, if we can mitigate the performance impact. diff --git a/src/Ryujinx.Graphics.Shader/Translation/ShaderDefinitions.cs b/src/Ryujinx.Graphics.Shader/Translation/ShaderDefinitions.cs index 3246e25940..f831ec940c 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/ShaderDefinitions.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/ShaderDefinitions.cs @@ -45,6 +45,8 @@ namespace Ryujinx.Graphics.Shader.Translation public bool YNegateEnabled => _graphicsState.YNegateEnabled; public bool OriginUpperLeft => _graphicsState.OriginUpperLeft; + public bool HalvePrimitiveId => _graphicsState.HalvePrimitiveId; + public ImapPixelType[] ImapTypes { get; } public bool IaIndexing { get; private set; } public bool OaIndexing { get; private set; } diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index b46ba9c461..8ef05de36c 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -691,6 +691,7 @@ namespace Ryujinx.Graphics.Vulkan supportsBgraFormat: true, supportsR4G4Format: false, supportsR4G4B4A4Format: supportsR4G4B4A4Format, + supportsScaledVertexFormats: FormatCapabilities.SupportsScaledVertexFormats(), supportsSnormBufferTextureFormat: true, supports5BitComponentFormat: supports5BitComponentFormat, supportsSparseBuffer: features2.Features.SparseBinding && mainQueueProperties.QueueFlags.HasFlag(QueueFlags.SparseBindingBit), @@ -705,7 +706,7 @@ namespace Ryujinx.Graphics.Vulkan supportsMismatchingViewFormat: true, supportsCubemapView: !IsAmdGcn, supportsNonConstantTextureOffset: false, - supportsScaledVertexFormats: FormatCapabilities.SupportsScaledVertexFormats(), + supportsQuads: false, supportsSeparateSampler: true, supportsShaderBallot: false, supportsShaderBarrierDivergence: Vendor != Vendor.Intel, From 075575200d693a5d24bd03a4220238e1e50f1ef6 Mon Sep 17 00:00:00 2001 From: Gavin Zyonse Date: Tue, 14 May 2024 16:58:48 +0200 Subject: [PATCH 78/87] Update compatibility information in README.md (#6801) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f2f3cb001e..7f2294d311 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,8 @@ ## Compatibility -As of October 2023, Ryujinx has been tested on approximately 4,200 titles; -over 4,150 boot past menus and into gameplay, with roughly 3,500 of those being considered playable. +As of May 2024, Ryujinx has been tested on approximately 4,300 titles; +over 4,100 boot past menus and into gameplay, with roughly 3,550 of those being considered playable. You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues). From 2b6cc4b3536693d222b695295c9db4715ca3f570 Mon Sep 17 00:00:00 2001 From: yell0wsuit <5692900+yell0wsuit@users.noreply.github.com> Date: Tue, 14 May 2024 22:00:03 +0700 Subject: [PATCH 79/87] Add the "Auto" theme option in setting (#6611) * Add "Follow OS theme" option * Update App.axaml.cs * Add "Follow OS theme" option * Update App.axaml.cs * Remove `this` * Remove annotation for nullable reference * Change into switch expression to make it concise * Change comments to XML docs * Update en_US.json * Fix icons in About dialog do not response to "auto" theme The theme icons seemingly use Dark variant event when the OS theme is light. In addition, I added ThemeManager common to make it accessible for both App and AboutWindow * Newline at the end * newline moment * Update ThemeManager.cs * bait to switch to lf * change to lf * temp. revert * Add back ThemeManager.cs common, pls pass the format check * I found the mistake: should have put `ThemeManager.OnThemeChanged();` in try block Finally solve the formatting check * test formatting * Update App.axaml.cs * Ok i seem to forget to add version lol * Fix info CA1816 --- src/Ryujinx/App.axaml.cs | 34 +++++++++++- src/Ryujinx/Assets/Locales/en_US.json | 1 + src/Ryujinx/Common/ThemeManager.cs | 14 +++++ .../UI/ViewModels/AboutWindowViewModel.cs | 52 +++++++++++++------ .../UI/ViewModels/SettingsViewModel.cs | 16 +++++- .../UI/Views/Settings/SettingsUIView.axaml | 3 ++ src/Ryujinx/UI/Windows/MainWindow.axaml.cs | 29 +++++++++++ 7 files changed, 128 insertions(+), 21 deletions(-) create mode 100644 src/Ryujinx/Common/ThemeManager.cs diff --git a/src/Ryujinx/App.axaml.cs b/src/Ryujinx/App.axaml.cs index 387a6dc145..24d8a70a17 100644 --- a/src/Ryujinx/App.axaml.cs +++ b/src/Ryujinx/App.axaml.cs @@ -1,8 +1,10 @@ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; +using Avalonia.Platform; using Avalonia.Styling; using Avalonia.Threading; +using Ryujinx.Ava.Common; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Windows; @@ -84,7 +86,7 @@ namespace Ryujinx.Ava ApplyConfiguredTheme(); } - private void ApplyConfiguredTheme() + public void ApplyConfiguredTheme() { try { @@ -92,13 +94,18 @@ namespace Ryujinx.Ava if (string.IsNullOrWhiteSpace(baseStyle)) { - ConfigurationState.Instance.UI.BaseStyle.Value = "Dark"; + ConfigurationState.Instance.UI.BaseStyle.Value = "Auto"; baseStyle = ConfigurationState.Instance.UI.BaseStyle; } + ThemeVariant systemTheme = DetectSystemTheme(); + + ThemeManager.OnThemeChanged(); + RequestedThemeVariant = baseStyle switch { + "Auto" => systemTheme, "Light" => ThemeVariant.Light, "Dark" => ThemeVariant.Dark, _ => ThemeVariant.Default, @@ -111,5 +118,28 @@ namespace Ryujinx.Ava ShowRestartDialog(); } } + + /// + /// Converts a PlatformThemeVariant value to the corresponding ThemeVariant value. + /// + public static ThemeVariant ConvertThemeVariant(PlatformThemeVariant platformThemeVariant) => + platformThemeVariant switch + { + PlatformThemeVariant.Dark => ThemeVariant.Dark, + PlatformThemeVariant.Light => ThemeVariant.Light, + _ => ThemeVariant.Default, + }; + + public static ThemeVariant DetectSystemTheme() + { + if (Application.Current is App app) + { + var colorValues = app.PlatformSettings.GetColorValues(); + + return ConvertThemeVariant(colorValues.ThemeVariant); + } + + return ThemeVariant.Default; + } } } diff --git a/src/Ryujinx/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json index 2299d9e665..8df0f96a14 100644 --- a/src/Ryujinx/Assets/Locales/en_US.json +++ b/src/Ryujinx/Assets/Locales/en_US.json @@ -404,6 +404,7 @@ "GameListContextMenuToggleFavorite": "Toggle Favorite", "GameListContextMenuToggleFavoriteToolTip": "Toggle Favorite status of Game", "SettingsTabGeneralTheme": "Theme:", + "SettingsTabGeneralThemeAuto": "Auto", "SettingsTabGeneralThemeDark": "Dark", "SettingsTabGeneralThemeLight": "Light", "ControllerSettingsConfigureGeneral": "Configure", diff --git a/src/Ryujinx/Common/ThemeManager.cs b/src/Ryujinx/Common/ThemeManager.cs new file mode 100644 index 0000000000..8c52c2a66a --- /dev/null +++ b/src/Ryujinx/Common/ThemeManager.cs @@ -0,0 +1,14 @@ +using System; + +namespace Ryujinx.Ava.Common +{ + public static class ThemeManager + { + public static event EventHandler ThemeChanged; + + public static void OnThemeChanged() + { + ThemeChanged?.Invoke(null, EventArgs.Empty); + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs index 6020f40e0c..f8fd5b7de4 100644 --- a/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs @@ -1,6 +1,8 @@ using Avalonia.Media.Imaging; using Avalonia.Platform; +using Avalonia.Styling; using Avalonia.Threading; +using Ryujinx.Ava.Common; using Ryujinx.Ava.Common.Locale; using Ryujinx.Common.Utilities; using Ryujinx.UI.Common.Configuration; @@ -11,7 +13,7 @@ using System.Threading.Tasks; namespace Ryujinx.Ava.UI.ViewModels { - public class AboutWindowViewModel : BaseModel + public class AboutWindowViewModel : BaseModel, IDisposable { private Bitmap _githubLogo; private Bitmap _discordLogo; @@ -86,23 +88,39 @@ namespace Ryujinx.Ava.UI.ViewModels public AboutWindowViewModel() { Version = Program.Version; - - if (ConfigurationState.Instance.UI.BaseStyle.Value == "Light") - { - GithubLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_GitHub_Light.png?assembly=Ryujinx.UI.Common"))); - DiscordLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Discord_Light.png?assembly=Ryujinx.UI.Common"))); - PatreonLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Patreon_Light.png?assembly=Ryujinx.UI.Common"))); - TwitterLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Twitter_Light.png?assembly=Ryujinx.UI.Common"))); - } - else - { - GithubLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_GitHub_Dark.png?assembly=Ryujinx.UI.Common"))); - DiscordLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Discord_Dark.png?assembly=Ryujinx.UI.Common"))); - PatreonLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Patreon_Dark.png?assembly=Ryujinx.UI.Common"))); - TwitterLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Twitter_Dark.png?assembly=Ryujinx.UI.Common"))); - } - + UpdateLogoTheme(ConfigurationState.Instance.UI.BaseStyle.Value); Dispatcher.UIThread.InvokeAsync(DownloadPatronsJson); + + ThemeManager.ThemeChanged += ThemeManager_ThemeChanged; + } + + private void ThemeManager_ThemeChanged(object sender, EventArgs e) + { + Dispatcher.UIThread.Post(() => UpdateLogoTheme(ConfigurationState.Instance.UI.BaseStyle.Value)); + } + + private void UpdateLogoTheme(string theme) + { + bool isDarkTheme = theme == "Dark" || (theme == "Auto" && App.DetectSystemTheme() == ThemeVariant.Dark); + + string basePath = "resm:Ryujinx.UI.Common.Resources."; + string themeSuffix = isDarkTheme ? "Dark.png" : "Light.png"; + + GithubLogo = LoadBitmap($"{basePath}Logo_GitHub_{themeSuffix}?assembly=Ryujinx.UI.Common"); + DiscordLogo = LoadBitmap($"{basePath}Logo_Discord_{themeSuffix}?assembly=Ryujinx.UI.Common"); + PatreonLogo = LoadBitmap($"{basePath}Logo_Patreon_{themeSuffix}?assembly=Ryujinx.UI.Common"); + TwitterLogo = LoadBitmap($"{basePath}Logo_Twitter_{themeSuffix}?assembly=Ryujinx.UI.Common"); + } + + private Bitmap LoadBitmap(string uri) + { + return new Bitmap(Avalonia.Platform.AssetLoader.Open(new Uri(uri))); + } + + public void Dispose() + { + ThemeManager.ThemeChanged -= ThemeManager_ThemeChanged; + GC.SuppressFinalize(this); } private async Task DownloadPatronsJson() diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index 0f43d0f7fc..70e5fa5c74 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -397,7 +397,13 @@ namespace Ryujinx.Ava.UI.ViewModels GameDirectories.Clear(); GameDirectories.AddRange(config.UI.GameDirs.Value); - BaseStyleIndex = config.UI.BaseStyle == "Light" ? 0 : 1; + BaseStyleIndex = config.UI.BaseStyle.Value switch + { + "Auto" => 0, + "Light" => 1, + "Dark" => 2, + _ => 0 + }; // Input EnableDockedMode = config.System.EnableDockedMode; @@ -486,7 +492,13 @@ namespace Ryujinx.Ava.UI.ViewModels config.UI.GameDirs.Value = gameDirs; } - config.UI.BaseStyle.Value = BaseStyleIndex == 0 ? "Light" : "Dark"; + config.UI.BaseStyle.Value = BaseStyleIndex switch + { + 0 => "Auto", + 1 => "Light", + 2 => "Dark", + _ => "Auto" + }; // Input config.System.EnableDockedMode.Value = EnableDockedMode; diff --git a/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml index b60058fcbf..f9b9be44b2 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml +++ b/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml @@ -65,6 +65,9 @@ + + + diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs index b1b7a4853d..7de8a49a0c 100644 --- a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs @@ -2,6 +2,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Interactivity; +using Avalonia.Platform; using Avalonia.Threading; using FluentAvalonia.UI.Controls; using Ryujinx.Ava.Common; @@ -92,6 +93,29 @@ namespace Ryujinx.Ava.UI.Windows } } + /// + /// Event handler for detecting OS theme change when using "Follow OS theme" option + /// + private void OnPlatformColorValuesChanged(object sender, PlatformColorValues e) + { + if (Application.Current is App app) + { + app.ApplyConfiguredTheme(); + } + } + + protected override void OnClosed(EventArgs e) + { + base.OnClosed(e); + if (PlatformSettings != null) + { + /// + /// Unsubscribe to the ColorValuesChanged event + /// + PlatformSettings.ColorValuesChanged -= OnPlatformColorValuesChanged; + } + } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); @@ -390,6 +414,11 @@ namespace Ryujinx.Ava.UI.Windows Initialize(); + /// + /// Subscribe to the ColorValuesChanged event + /// + PlatformSettings.ColorValuesChanged += OnPlatformColorValuesChanged; + ViewModel.Initialize( ContentManager, StorageProvider, From 2ef4f92b0793feb7073ed85b7f7dc08dca6f14e9 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Tue, 14 May 2024 12:06:36 -0300 Subject: [PATCH 80/87] Make TextureGroup.ClearModified thread safe (#6686) --- src/Ryujinx.Graphics.Gpu/Image/Texture.cs | 2 +- src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs | 18 ++++++++++++------ .../Image/TextureGroupHandle.cs | 6 +++--- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs index e67caea81f..dde28dbd77 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -390,7 +390,7 @@ namespace Ryujinx.Graphics.Gpu.Image { _views.Remove(texture); - Group.RemoveView(texture); + Group.RemoveView(_views, texture); texture._viewStorage = texture; diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs index de9c47c976..4e1133d1af 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs @@ -88,9 +88,9 @@ namespace Ryujinx.Graphics.Gpu.Image private MultiRange TextureRange => Storage.Range; /// - /// The views list from the storage texture. + /// The views array from the storage texture. /// - private List _views; + private Texture[] _views; private TextureGroupHandle[] _handles; private bool[] _loadNeeded; @@ -1074,7 +1074,7 @@ namespace Ryujinx.Graphics.Gpu.Image public void UpdateViews(List views, Texture texture) { // This is saved to calculate overlapping views for each handle. - _views = views; + _views = views.ToArray(); bool layerViews = _hasLayerViews; bool mipViews = _hasMipViews; @@ -1136,9 +1136,13 @@ namespace Ryujinx.Graphics.Gpu.Image /// /// Removes a view from the group, removing it from all overlap lists. /// + /// The views list of the storage texture /// View to remove from the group - public void RemoveView(Texture view) + public void RemoveView(List views, Texture view) { + // This is saved to calculate overlapping views for each handle. + _views = views.ToArray(); + int offset = FindOffset(view); foreach (TextureGroupHandle handle in _handles) @@ -1605,9 +1609,11 @@ namespace Ryujinx.Graphics.Gpu.Image Storage.SignalModifiedDirty(); - if (_views != null) + Texture[] views = _views; + + if (views != null) { - foreach (Texture texture in _views) + foreach (Texture texture in views) { texture.SignalModifiedDirty(); } diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs index 0af6b7ca83..860922d592 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs @@ -121,7 +121,7 @@ namespace Ryujinx.Graphics.Gpu.Image public TextureGroupHandle(TextureGroup group, int offset, ulong size, - List views, + IEnumerable views, int firstLayer, int firstLevel, int baseSlice, @@ -201,8 +201,8 @@ namespace Ryujinx.Graphics.Gpu.Image /// Calculate a list of which views overlap this handle. /// /// The parent texture group, used to find a view's base CPU VA offset - /// The list of views to search for overlaps - public void RecalculateOverlaps(TextureGroup group, List views) + /// The views to search for overlaps + public void RecalculateOverlaps(TextureGroup group, IEnumerable views) { // Overlaps can be accessed from the memory tracking signal handler, so access must be atomic. lock (Overlaps) From a3dc295c5f867bddb56a38f3a848ceb61ff30d32 Mon Sep 17 00:00:00 2001 From: TSRBerry <20988865+TSRBerry@users.noreply.github.com> Date: Tue, 14 May 2024 17:14:39 +0200 Subject: [PATCH 81/87] Disable keyboard controller input while swkbd is open (foreground) (#6646) * Block input updates while swkbd is open in foreground mode * Flush internal driver state before unblocking input updates * Rename Flush to Clear and remove unnecessary attribute --- src/Ryujinx.Gtk3/Input/GTK3/GTK3KeyboardDriver.cs | 5 +++++ src/Ryujinx.Gtk3/UI/Applet/GtkHostUIHandler.cs | 3 +++ src/Ryujinx.Input/HLE/NpadManager.cs | 5 +++++ src/Ryujinx.Input/IGamepadDriver.cs | 6 ++++++ src/Ryujinx/Input/AvaloniaKeyboard.cs | 2 +- src/Ryujinx/Input/AvaloniaKeyboardDriver.cs | 2 +- src/Ryujinx/UI/Applet/AvaHostUIHandler.cs | 2 ++ 7 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx.Gtk3/Input/GTK3/GTK3KeyboardDriver.cs b/src/Ryujinx.Gtk3/Input/GTK3/GTK3KeyboardDriver.cs index e502254be1..bd71c7933f 100644 --- a/src/Ryujinx.Gtk3/Input/GTK3/GTK3KeyboardDriver.cs +++ b/src/Ryujinx.Gtk3/Input/GTK3/GTK3KeyboardDriver.cs @@ -81,6 +81,11 @@ namespace Ryujinx.Input.GTK3 return _pressedKeys.Contains(nativeKey); } + public void Clear() + { + _pressedKeys.Clear(); + } + public IGamepad GetGamepad(string id) { if (!_keyboardIdentifers[0].Equals(id)) diff --git a/src/Ryujinx.Gtk3/UI/Applet/GtkHostUIHandler.cs b/src/Ryujinx.Gtk3/UI/Applet/GtkHostUIHandler.cs index 1d918d21b6..b3f509a090 100644 --- a/src/Ryujinx.Gtk3/UI/Applet/GtkHostUIHandler.cs +++ b/src/Ryujinx.Gtk3/UI/Applet/GtkHostUIHandler.cs @@ -107,6 +107,8 @@ namespace Ryujinx.UI.Applet swkbdDialog.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax); swkbdDialog.SetInputValidation(args.KeyboardMode); + ((MainWindow)_parent).RendererWidget.NpadManager.BlockInputUpdates(); + if (swkbdDialog.Run() == (int)ResponseType.Ok) { inputText = swkbdDialog.InputEntry.Text; @@ -128,6 +130,7 @@ namespace Ryujinx.UI.Applet }); dialogCloseEvent.WaitOne(); + ((MainWindow)_parent).RendererWidget.NpadManager.UnblockInputUpdates(); userText = error ? null : inputText; diff --git a/src/Ryujinx.Input/HLE/NpadManager.cs b/src/Ryujinx.Input/HLE/NpadManager.cs index 4c7bb8b7a4..2409ecf22c 100644 --- a/src/Ryujinx.Input/HLE/NpadManager.cs +++ b/src/Ryujinx.Input/HLE/NpadManager.cs @@ -174,6 +174,11 @@ namespace Ryujinx.Input.HLE { lock (_lock) { + foreach (InputConfig inputConfig in _inputConfig) + { + _controllers[(int)inputConfig.PlayerIndex].GamepadDriver.Clear(); + } + _blockInputUpdates = false; } } diff --git a/src/Ryujinx.Input/IGamepadDriver.cs b/src/Ryujinx.Input/IGamepadDriver.cs index 67b01c26ca..ff4d36993c 100644 --- a/src/Ryujinx.Input/IGamepadDriver.cs +++ b/src/Ryujinx.Input/IGamepadDriver.cs @@ -33,5 +33,11 @@ namespace Ryujinx.Input /// The unique id of the gamepad /// An instance of associated to the gamepad id given or null if not found IGamepad GetGamepad(string id); + + /// + /// Flush the internal state of the driver. + /// + /// Does nothing by default. + void Clear() { } } } diff --git a/src/Ryujinx/Input/AvaloniaKeyboard.cs b/src/Ryujinx/Input/AvaloniaKeyboard.cs index fbaaaabab8..ff88de79e4 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboard.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboard.cs @@ -195,7 +195,7 @@ namespace Ryujinx.Ava.Input public void Clear() { - _driver?.ResetKeys(); + _driver?.Clear(); } public void Dispose() { } diff --git a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs index e9e71b99bd..9f87e821ad 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs @@ -94,7 +94,7 @@ namespace Ryujinx.Ava.Input return _pressedKeys.Contains(nativeKey); } - public void ResetKeys() + public void Clear() { _pressedKeys.Clear(); } diff --git a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs index 4bcc35a7a5..4bcf8eb949 100644 --- a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs +++ b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs @@ -122,6 +122,7 @@ namespace Ryujinx.Ava.UI.Applet { try { + _parent.ViewModel.AppHost.NpadManager.BlockInputUpdates(); var response = await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.SoftwareKeyboard], args); if (response.Result == UserResult.Ok) @@ -143,6 +144,7 @@ namespace Ryujinx.Ava.UI.Applet }); dialogCloseEvent.WaitOne(); + _parent.ViewModel.AppHost.NpadManager.UnblockInputUpdates(); userText = error ? null : inputText; From cd78adf07f14ccb8b19a9f539db963dea5976312 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Tue, 14 May 2024 12:23:13 -0300 Subject: [PATCH 82/87] Add missing lock on texture cache UpdateMapping method (#6657) --- .../Image/TextureCache.cs | 95 ++++++++++++++++--- 1 file changed, 84 insertions(+), 11 deletions(-) diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs index 5743c89c0d..b9ff060e25 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs @@ -8,6 +8,7 @@ using Ryujinx.Graphics.Texture; using Ryujinx.Memory.Range; using System; using System.Collections.Generic; +using System.Threading; namespace Ryujinx.Graphics.Gpu.Image { @@ -39,6 +40,8 @@ namespace Ryujinx.Graphics.Gpu.Image private readonly MultiRangeList _textures; private readonly HashSet _partiallyMappedTextures; + private readonly ReaderWriterLockSlim _texturesLock; + private Texture[] _textureOverlaps; private OverlapInfo[] _overlapInfo; @@ -57,6 +60,8 @@ namespace Ryujinx.Graphics.Gpu.Image _textures = new MultiRangeList(); _partiallyMappedTextures = new HashSet(); + _texturesLock = new ReaderWriterLockSlim(); + _textureOverlaps = new Texture[OverlapsBufferInitialCapacity]; _overlapInfo = new OverlapInfo[OverlapsBufferInitialCapacity]; @@ -75,10 +80,16 @@ namespace Ryujinx.Graphics.Gpu.Image MultiRange unmapped = ((MemoryManager)sender).GetPhysicalRegions(e.Address, e.Size); - lock (_textures) + _texturesLock.EnterReadLock(); + + try { overlapCount = _textures.FindOverlaps(unmapped, ref overlaps); } + finally + { + _texturesLock.ExitReadLock(); + } if (overlapCount > 0) { @@ -217,7 +228,18 @@ namespace Ryujinx.Graphics.Gpu.Image public bool UpdateMapping(Texture texture, MultiRange range) { // There cannot be an existing texture compatible with this mapping in the texture cache already. - int overlapCount = _textures.FindOverlaps(range, ref _textureOverlaps); + int overlapCount; + + _texturesLock.EnterReadLock(); + + try + { + overlapCount = _textures.FindOverlaps(range, ref _textureOverlaps); + } + finally + { + _texturesLock.ExitReadLock(); + } for (int i = 0; i < overlapCount; i++) { @@ -231,11 +253,20 @@ namespace Ryujinx.Graphics.Gpu.Image } } - _textures.Remove(texture); + _texturesLock.EnterWriteLock(); - texture.ReplaceRange(range); + try + { + _textures.Remove(texture); - _textures.Add(texture); + texture.ReplaceRange(range); + + _textures.Add(texture); + } + finally + { + _texturesLock.ExitWriteLock(); + } return true; } @@ -611,11 +642,17 @@ namespace Ryujinx.Graphics.Gpu.Image int sameAddressOverlapsCount; - lock (_textures) + _texturesLock.EnterReadLock(); + + try { // Try to find a perfect texture match, with the same address and parameters. sameAddressOverlapsCount = _textures.FindOverlaps(address, ref _textureOverlaps); } + finally + { + _texturesLock.ExitReadLock(); + } Texture texture = null; @@ -698,10 +735,16 @@ namespace Ryujinx.Graphics.Gpu.Image if (info.Target != Target.TextureBuffer) { - lock (_textures) + _texturesLock.EnterReadLock(); + + try { overlapsCount = _textures.FindOverlaps(range.Value, ref _textureOverlaps); } + finally + { + _texturesLock.ExitReadLock(); + } } if (_overlapInfo.Length != _textureOverlaps.Length) @@ -1025,10 +1068,16 @@ namespace Ryujinx.Graphics.Gpu.Image _cache.Add(texture); } - lock (_textures) + _texturesLock.EnterWriteLock(); + + try { _textures.Add(texture); } + finally + { + _texturesLock.ExitWriteLock(); + } if (partiallyMapped) { @@ -1091,7 +1140,19 @@ namespace Ryujinx.Graphics.Gpu.Image return null; } - int addressMatches = _textures.FindOverlaps(address, ref _textureOverlaps); + int addressMatches; + + _texturesLock.EnterReadLock(); + + try + { + addressMatches = _textures.FindOverlaps(address, ref _textureOverlaps); + } + finally + { + _texturesLock.ExitReadLock(); + } + Texture textureMatch = null; for (int i = 0; i < addressMatches; i++) @@ -1232,10 +1293,16 @@ namespace Ryujinx.Graphics.Gpu.Image /// The texture to be removed public void RemoveTextureFromCache(Texture texture) { - lock (_textures) + _texturesLock.EnterWriteLock(); + + try { _textures.Remove(texture); } + finally + { + _texturesLock.ExitWriteLock(); + } lock (_partiallyMappedTextures) { @@ -1324,13 +1391,19 @@ namespace Ryujinx.Graphics.Gpu.Image /// public void Dispose() { - lock (_textures) + _texturesLock.EnterReadLock(); + + try { foreach (Texture texture in _textures) { texture.Dispose(); } } + finally + { + _texturesLock.ExitReadLock(); + } } } } From 47639e6eebe6f24bc6e0796fa27a750d9e99bd87 Mon Sep 17 00:00:00 2001 From: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> Date: Tue, 14 May 2024 11:36:11 -0400 Subject: [PATCH 83/87] Bump Avalonia.Svg (#6603) * Bump Avalonia.Svg * Remove using * Bump Version * Remove other reload --- Directory.Packages.props | 4 ++-- src/Ryujinx/UI/Applet/ControllerAppletDialog.axaml.cs | 5 +---- src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs | 4 +--- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index ef274125a4..d04e237e04 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -8,8 +8,8 @@ - - + + diff --git a/src/Ryujinx/UI/Applet/ControllerAppletDialog.axaml.cs b/src/Ryujinx/UI/Applet/ControllerAppletDialog.axaml.cs index 5a98b16456..6b999b1f44 100644 --- a/src/Ryujinx/UI/Applet/ControllerAppletDialog.axaml.cs +++ b/src/Ryujinx/UI/Applet/ControllerAppletDialog.axaml.cs @@ -9,7 +9,6 @@ using Ryujinx.Ava.UI.Windows; using Ryujinx.Common; using Ryujinx.HLE.HOS.Applets; using Ryujinx.HLE.HOS.Services.Hid; -using System; using System.Linq; using System.Threading.Tasks; @@ -104,9 +103,7 @@ namespace Ryujinx.Ava.UI.Applet if (!string.IsNullOrWhiteSpace(path)) { - SvgSource source = new(default(Uri)); - - source.Load(EmbeddedResources.GetStream(path)); + SvgSource source = SvgSource.LoadFromStream(EmbeddedResources.GetStream(path)); image.Source = source; } diff --git a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs index 74da459793..89cc6496d5 100644 --- a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs @@ -181,9 +181,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input if (!string.IsNullOrWhiteSpace(_controllerImage)) { - SvgSource source = new(default(Uri)); - - source.Load(EmbeddedResources.GetStream(_controllerImage)); + SvgSource source = SvgSource.LoadFromStream(EmbeddedResources.GetStream(_controllerImage)); image.Source = source; } From 2ca0b17339ada361e5f8edbf1f8dfab741e496f5 Mon Sep 17 00:00:00 2001 From: Ac_K Date: Tue, 14 May 2024 17:47:03 +0200 Subject: [PATCH 84/87] New Crowdin updates (#6590) * New translations en_us.json (Arabic) * New translations en_us.json (Russian) * New translations en_us.json (Chinese Traditional) * New translations en_us.json (French) * New translations en_us.json (Russian) * New translations en_us.json (Spanish) * New translations en_us.json (Arabic) * New translations en_us.json (German) * New translations en_us.json (Greek) * New translations en_us.json (Hebrew) * New translations en_us.json (Italian) * New translations en_us.json (Japanese) * New translations en_us.json (Korean) * New translations en_us.json (Polish) * New translations en_us.json (Russian) * New translations en_us.json (Turkish) * New translations en_us.json (Ukrainian) * New translations en_us.json (Chinese Simplified) * New translations en_us.json (Chinese Traditional) * New translations en_us.json (Portuguese, Brazilian) * New translations en_us.json (Thai) * New translations en_us.json (French) * New translations en_us.json (Arabic) * New translations en_us.json (Italian) * New translations en_us.json (Korean) * New translations en_us.json (Russian) * New translations en_us.json (Ukrainian) * New translations en_us.json (Chinese Simplified) * New translations en_us.json (Arabic) * New translations en_us.json (Polish) * New translations en_us.json (Turkish) * New translations en_us.json (Arabic) * New translations en_us.json (Chinese Traditional) * New translations en_us.json (Russian) * New translations en_us.json (French) * New translations en_us.json (Thai) * New translations en_us.json (Spanish) * New translations en_us.json (Arabic) * New translations en_us.json (German) * New translations en_us.json (Greek) * New translations en_us.json (Hebrew) * New translations en_us.json (Italian) * New translations en_us.json (Japanese) * New translations en_us.json (Korean) * New translations en_us.json (Polish) * New translations en_us.json (Russian) * New translations en_us.json (Turkish) * New translations en_us.json (Ukrainian) * New translations en_us.json (Chinese Simplified) * New translations en_us.json (Chinese Traditional) * New translations en_us.json (Portuguese, Brazilian) * New translations en_us.json (Thai) * New translations en_us.json (French) * New translations en_us.json (Arabic) * New translations en_us.json (Chinese Traditional) * New translations en_us.json (Arabic) * New translations en_us.json (Italian) * New translations en_us.json (Arabic) * New translations en_us.json (Italian) * New translations en_us.json (Spanish) * New translations en_us.json (Russian) * New translations en_us.json (Russian) * New translations en_us.json (Thai) * New translations en_us.json (Spanish) * New translations en_us.json (Arabic) * New translations en_us.json (German) * New translations en_us.json (Greek) * New translations en_us.json (Hebrew) * New translations en_us.json (Italian) * New translations en_us.json (Japanese) * New translations en_us.json (Korean) * New translations en_us.json (Polish) * New translations en_us.json (Russian) * New translations en_us.json (Turkish) * New translations en_us.json (Ukrainian) * New translations en_us.json (Chinese Simplified) * New translations en_us.json (Chinese Traditional) * New translations en_us.json (Portuguese, Brazilian) * New translations en_us.json (Thai) * New translations en_us.json (French) * New translations en_us.json (Chinese Traditional) * New translations en_us.json (Arabic) * New translations en_us.json (Russian) * New translations en_us.json (Turkish) * New translations en_us.json (Chinese Simplified) * New translations en_us.json (Russian) * New translations en_us.json (Chinese Simplified) * New translations en_us.json (Russian) * New translations en_us.json (Portuguese, Brazilian) * New translations en_us.json (German) --- src/Ryujinx/Assets/Locales/ar_SA.json | 547 +++++++++++++++----------- src/Ryujinx/Assets/Locales/de_DE.json | 107 +++++ src/Ryujinx/Assets/Locales/el_GR.json | 107 +++++ src/Ryujinx/Assets/Locales/es_ES.json | 123 +++++- src/Ryujinx/Assets/Locales/fr_FR.json | 113 +++++- src/Ryujinx/Assets/Locales/he_IL.json | 107 +++++ src/Ryujinx/Assets/Locales/it_IT.json | 109 ++++- src/Ryujinx/Assets/Locales/ja_JP.json | 107 +++++ src/Ryujinx/Assets/Locales/ko_KR.json | 107 +++++ src/Ryujinx/Assets/Locales/pl_PL.json | 119 +++++- src/Ryujinx/Assets/Locales/pt_BR.json | 107 +++++ src/Ryujinx/Assets/Locales/ru_RU.json | 261 ++++++++---- src/Ryujinx/Assets/Locales/th_TH.json | 325 ++++++++++----- src/Ryujinx/Assets/Locales/tr_TR.json | 135 ++++++- src/Ryujinx/Assets/Locales/uk_UA.json | 107 +++++ src/Ryujinx/Assets/Locales/zh_CN.json | 127 +++++- src/Ryujinx/Assets/Locales/zh_TW.json | 139 ++++++- 17 files changed, 2283 insertions(+), 464 deletions(-) diff --git a/src/Ryujinx/Assets/Locales/ar_SA.json b/src/Ryujinx/Assets/Locales/ar_SA.json index 51df3a0522..73e55633bb 100644 --- a/src/Ryujinx/Assets/Locales/ar_SA.json +++ b/src/Ryujinx/Assets/Locales/ar_SA.json @@ -1,38 +1,42 @@ { - "Language": "العربية", - "MenuBarFileOpenApplet": "فتح التطبيق المُصغَّر", - "MenuBarFileOpenAppletOpenMiiAppletToolTip": "افتح تطبيق محرر الـMii المُصغَّر في الوضع المستقل", + "Language": "اَلْعَرَبِيَّةُ", + "MenuBarFileOpenApplet": "فتح التطبيق المصغر", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "‫افتح تطبيق تحرير Mii في الوضع المستقل", "SettingsTabInputDirectMouseAccess": "الوصول المباشر للفأرة", "SettingsTabSystemMemoryManagerMode": "وضع إدارة الذاكرة:", "SettingsTabSystemMemoryManagerModeSoftware": "البرنامج", "SettingsTabSystemMemoryManagerModeHost": "المُضيف (سريع)", - "SettingsTabSystemMemoryManagerModeHostUnchecked": "المضيف غير محدد (سريع، غير آمن)", - "SettingsTabSystemUseHypervisor": "استخدم الهايبرڤايزور", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "المضيف (غير مفحوص) (أسرع، غير آمن)", + "SettingsTabSystemUseHypervisor": "استخدم مراقب الأجهزة الافتراضية", "MenuBarFile": "_ملف", "MenuBarFileOpenFromFile": "_تحميل تطبيق من ملف", - "MenuBarFileOpenUnpacked": "تحميل لعبه غير محزومه", - "MenuBarFileOpenEmuFolder": "فتح مجلد Ryujinx", + "MenuBarFileOpenUnpacked": "تحميل لُعْبَة غير محزومة", + "MenuBarFileOpenEmuFolder": "‫فتح مجلد Ryujinx", "MenuBarFileOpenLogsFolder": "فتح مجلد السجلات", "MenuBarFileExit": "_خروج", "MenuBarOptions": "_خيارات", - "MenuBarOptionsToggleFullscreen": "وضع ملء الشاشة", + "MenuBarOptionsToggleFullscreen": "التبديل إلى وضع ملء الشاشة", "MenuBarOptionsStartGamesInFullscreen": "ابدأ الألعاب في وضع ملء الشاشة", "MenuBarOptionsStopEmulation": "إيقاف المحاكاة", - "MenuBarOptionsSettings": "الإعدادات", - "MenuBarOptionsManageUserProfiles": "إدارة الملفات الشخصية للمستخدم", - "MenuBarActions": "الإجراءات", + "MenuBarOptionsSettings": "_الإعدادات", + "MenuBarOptionsManageUserProfiles": "_إدارة الملفات الشخصية للمستخدم", + "MenuBarActions": "_الإجراءات", "MenuBarOptionsSimulateWakeUpMessage": "محاكاة رسالة الاستيقاظ", - "MenuBarActionsScanAmiibo": "فحص Amiibo", - "MenuBarTools": "الأدوات", - "MenuBarToolsInstallFirmware": "تثبيت البرامج الثابتة", - "MenuBarFileToolsInstallFirmwareFromFile": "تثبيت البرنامج الثابت من XCI أو ZIP", + "MenuBarActionsScanAmiibo": "‫فحص Amiibo", + "MenuBarTools": "_الأدوات", + "MenuBarToolsInstallFirmware": "تثبيت البرنامج الثابت", + "MenuBarFileToolsInstallFirmwareFromFile": "تثبيت برنامج ثابت من XCI أو ZIP", "MenuBarFileToolsInstallFirmwareFromDirectory": "تثبيت برنامج ثابت من مجلد", "MenuBarToolsManageFileTypes": "إدارة أنواع الملفات", "MenuBarToolsInstallFileTypes": "تثبيت أنواع الملفات", "MenuBarToolsUninstallFileTypes": "إزالة أنواع الملفات", + "MenuBarView": "_عرض", + "MenuBarViewWindow": "حجم النافذة", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", "MenuBarHelp": "_مساعدة", "MenuBarHelpCheckForUpdates": "تحقق من التحديثات", - "MenuBarHelpAbout": "عن البرنامج", + "MenuBarHelpAbout": "حول", "MenuSearch": "بحث...", "GameListHeaderFavorite": "مفضلة", "GameListHeaderIcon": "الأيقونة", @@ -40,62 +44,63 @@ "GameListHeaderDeveloper": "المطور", "GameListHeaderVersion": "الإصدار", "GameListHeaderTimePlayed": "وقت اللعب", - "GameListHeaderLastPlayed": "اخر تشغيل", - "GameListHeaderFileExtension": "امتداد الملف", + "GameListHeaderLastPlayed": "آخر مرة لُعبت", + "GameListHeaderFileExtension": "صيغة الملف", "GameListHeaderFileSize": "حجم الملف", "GameListHeaderPath": "المسار", "GameListContextMenuOpenUserSaveDirectory": "فتح مجلد حفظ المستخدم", "GameListContextMenuOpenUserSaveDirectoryToolTip": "يفتح المجلد الذي يحتوي على حفظ المستخدم للتطبيق", "GameListContextMenuOpenDeviceSaveDirectory": "فتح مجلد حفظ الجهاز", "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "يفتح المجلد الذي يحتوي على حفظ الجهاز للتطبيق", - "GameListContextMenuOpenBcatSaveDirectory": "فتح مجلد حفظ الـBCAT", - "GameListContextMenuOpenBcatSaveDirectoryToolTip": "يفتح المجلد الذي يحتوي على حفظ الـBCAT للتطبيق", - "GameListContextMenuManageTitleUpdates": "إدارة تحديثات العنوان", - "GameListContextMenuManageTitleUpdatesToolTip": "يفتح نافذة إدارة تحديث العنوان", + "GameListContextMenuOpenBcatSaveDirectory": "‫فتح مجلد حفظ الـBCAT", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "‫يفتح المجلد الذي يحتوي على حفظ الـBCAT للتطبيق", + "GameListContextMenuManageTitleUpdates": "إدارة تحديثات اللُعبة", + "GameListContextMenuManageTitleUpdatesToolTip": "يفتح نافذة إدارة تحديث اللُعبة", "GameListContextMenuManageDlc": "إدارة المحتوي الإضافي", "GameListContextMenuManageDlcToolTip": "يفتح نافذة إدارة المحتوي الإضافي", "GameListContextMenuCacheManagement": "إدارة ذاكرة التخزين المؤقت", - "GameListContextMenuCacheManagementPurgePptc": "إعادة بناء PPTC في قائمة الانتظار", - "GameListContextMenuCacheManagementPurgePptcToolTip": "تنشيط PPTC لإعادة البناء في وقت التمهيد عند بدء تشغيل اللعبة التالية", - "GameListContextMenuCacheManagementPurgeShaderCache": "إزالة ذاكرة التشغيل المؤقتة للمظللات ", - "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "حذف الذاكرة المؤقتة للمظللات الخاصة بالتطبيق", - "GameListContextMenuCacheManagementOpenPptcDirectory": "فتح مجلد PPTC", - "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "يفتح المجلد الذي يحتوي على الـPPTC للتطبيق", - "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "فتح مجلد الذاكرة المؤقتة للمظللات ", - "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "يفتح المجلد الذي يحتوي على ذاكرة التشغيل المؤقتة للمظللات الخاصة بالتطبيق", - "GameListContextMenuExtractData": "إستخراج البيانات", + "GameListContextMenuCacheManagementPurgePptc": "قائمة انتظار إعادة بناء الـ‫PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "تنشيط ‫PPTC لإعادة البناء في وقت الإقلاع عند بدء تشغيل اللعبة التالي", + "GameListContextMenuCacheManagementPurgeShaderCache": "تنظيف ذاكرة مرشحات الفيديو المؤقتة", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "يحذف ذاكرة مرشحات الفيديو المؤقتة الخاصة بالتطبيق", + "GameListContextMenuCacheManagementOpenPptcDirectory": "‫فتح مجلد PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "‫‫يفتح المجلد الذي يحتوي على ذاكرة التخزين المؤقت للترجمة المستمرة (PPTC) للتطبيق", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "فتح مجلد الذاكرة المؤقتة لمرشحات الفيديو ", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "يفتح المجلد الذي يحتوي على ذاكرة المظللات المؤقتة للتطبيق", + "GameListContextMenuExtractData": "استخراج البيانات", "GameListContextMenuExtractDataExeFS": "ExeFS", - "GameListContextMenuExtractDataExeFSToolTip": "إستخراج قسم ExeFS من التكوين الحالي للتطبيقات (بما في ذلك التحديثات)", + "GameListContextMenuExtractDataExeFSToolTip": "‫‫ استخراج قسم نظام الملفات القابل للتنفيذ (ExeFS) من الإعدادات الحالية للتطبيقات (يتضمن التحديثات)", "GameListContextMenuExtractDataRomFS": "RomFS", - "GameListContextMenuExtractDataRomFSToolTip": "استخراج قسم RomFS من التكوين الحالي للتطبيقات (بما في ذلك التحديثات)", + "GameListContextMenuExtractDataRomFSToolTip": "استخراج قسم RomFS من الإعدادات الحالية للتطبيقات (يتضمن التحديثات)", "GameListContextMenuExtractDataLogo": "شعار", - "GameListContextMenuExtractDataLogoToolTip": "استخراج قسم الشعار من التكوين الحالي للتطبيقات (بما في ذلك التحديثات)", + "GameListContextMenuExtractDataLogoToolTip": "استخراج قسم الشعار من الإعدادات الحالية للتطبيقات (يتضمن التحديثات)", "GameListContextMenuCreateShortcut": "إنشاء اختصار للتطبيق", - "GameListContextMenuCreateShortcutToolTip": "قم بإنشاء اختصار لسطح المكتب لتشغيل التطبيق المحدد", - "GameListContextMenuCreateShortcutToolTipMacOS": "قم بإنشاء اختصار في مجلد تطبيقات نظام التشغيل MacOS الذي يقوم بتشغيل التطبيق المحدد", - "GameListContextMenuOpenModsDirectory": "فتح مجلد التعديلات", - "GameListContextMenuOpenModsDirectoryToolTip": "يفتح المجلد الذي يحتوي على تعديلات التطبيق", - "GameListContextMenuOpenSdModsDirectory": "فتح مجلد تعديلات Atmosphere", - "GameListContextMenuOpenSdModsDirectoryToolTip": "يفتح دليل Atmosphere لبطاقة SD البديلة الذي يحتوي على تعديلات التطبيق. مفيد للتعديلات التي تم تعبئتها للأجهزة الحقيقية.", - "StatusBarGamesLoaded": "{0}/{1} الألعاب التي تم تحميلها", + "GameListContextMenuCreateShortcutToolTip": "أنشئ اختصار سطح مكتب لتشغيل التطبيق المحدد", + "GameListContextMenuCreateShortcutToolTipMacOS": "أنشئ اختصار يُشغل التطبيق المحدد في مجلد تطبيقات ‫macOS", + "GameListContextMenuOpenModsDirectory": "‫فتح مجلد التعديلات (Mods)", + "GameListContextMenuOpenModsDirectoryToolTip": "يفتح المجلد الذي يحتوي على تعديلات‫(mods) التطبيق", + "GameListContextMenuOpenSdModsDirectory": "فتح مجلد تعديلات‫(mods) أتموسفير", + "GameListContextMenuOpenSdModsDirectoryToolTip": "يفتح مجلد أتموسفير لبطاقة SD البديلة الذي يحتوي على تعديلات التطبيق. مفيد للتعديلات التي تم تعبئتها للأجهزة الحقيقية.", + "StatusBarGamesLoaded": "{0}/{1} لعبة تم تحميلها", "StatusBarSystemVersion": "إصدار النظام: {0}", "LinuxVmMaxMapCountDialogTitle": "الحد الأدنى لتعيينات الذاكرة المكتشفة", "LinuxVmMaxMapCountDialogTextPrimary": "هل ترغب في زيادة قيمة vm.max_map_count إلى {0}", - "LinuxVmMaxMapCountDialogTextSecondary": "قد تحاول بعض الألعاب إنشاء المزيد من تعيينات الذاكرة أكثر مما هو مسموح به حاليا. سيتحطم Ryujinx بمجرد تجاوز هذا الحد.", + "LinuxVmMaxMapCountDialogTextSecondary": "قد تحاول بعض الألعاب إنشاء المزيد من تعيينات الذاكرة أكثر مما هو مسموح به حاليا. سيغلق ريوجينكس بمجرد تجاوز هذا الحد.", "LinuxVmMaxMapCountDialogButtonUntilRestart": "نعم، حتى إعادة التشغيل التالية", - "LinuxVmMaxMapCountDialogButtonPersistent": "نعم، بشكل دائم", + "LinuxVmMaxMapCountDialogButtonPersistent": "نعم، دائمًا", "LinuxVmMaxMapCountWarningTextPrimary": "الحد الأقصى لمقدار تعيينات الذاكرة أقل من الموصى به.", - "LinuxVmMaxMapCountWarningTextSecondary": "القيمة الحالية لـ vm.max_map_count ({0}) أقل من {1}. قد تحاول بعض الألعاب إنشاء المزيد من تعيينات الذاكرة أكثر مما هو مسموح به حاليا. سيتحطم Ryujinx بمجرد تجاوز هذا الحد.\n\nقد ترغب في زيادة الحد اليدوي أو تثبيت pkexec، مما يسمح لـ Ryujinx بالمساعدة في ذلك.", + "LinuxVmMaxMapCountWarningTextSecondary": "القيمة الحالية لـ vm.max_map_count ({0}) أقل من {1}. قد تحاول بعض الألعاب إنشاء المزيد من تعيينات الذاكرة أكثر مما هو مسموح به حاليا. سيغلق ريوجينكس بمجرد تجاوز هذا الحد.\n\nقد ترغب إما في زيادة الحد يدويا أو تثبيت pkexec، مما يسمح لـ ريوجينكس بالمساعدة في ذلك.", "Settings": "إعدادات", "SettingsTabGeneral": "واجهة المستخدم", - "SettingsTabGeneralGeneral": "العامة", + "SettingsTabGeneralGeneral": "عام", "SettingsTabGeneralEnableDiscordRichPresence": "تمكين وجود ديسكورد الغني", "SettingsTabGeneralCheckUpdatesOnLaunch": "التحقق من وجود تحديثات عند التشغيل", "SettingsTabGeneralShowConfirmExitDialog": "إظهار مربع حوار \"تأكيد الخروج\"", + "SettingsTabGeneralRememberWindowState": "تذكر حجم/موضع النافذة", "SettingsTabGeneralHideCursor": "إخفاء المؤشر:", - "SettingsTabGeneralHideCursorNever": "مطلقاً", + "SettingsTabGeneralHideCursorNever": "مطلقا", "SettingsTabGeneralHideCursorOnIdle": "عند الخمول", - "SettingsTabGeneralHideCursorAlways": "دائماً", + "SettingsTabGeneralHideCursorAlways": "دائما", "SettingsTabGeneralGameDirectories": "مجلدات الألعاب", "SettingsTabGeneralAdd": "إضافة", "SettingsTabGeneralRemove": "إزالة", @@ -127,24 +132,24 @@ "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "إسبانية أمريكا اللاتينية", "SettingsTabSystemSystemLanguageSimplifiedChinese": "الصينية المبسطة", "SettingsTabSystemSystemLanguageTraditionalChinese": "الصينية التقليدية", - "SettingsTabSystemSystemTimeZone": "نظام التوقيت للنظام:", + "SettingsTabSystemSystemTimeZone": "النطاق الزمني للنظام:", "SettingsTabSystemSystemTime": "توقيت النظام:", "SettingsTabSystemEnableVsync": "VSync", - "SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)", - "SettingsTabSystemEnableFsIntegrityChecks": "FS Integrity Checks", + "SettingsTabSystemEnablePptc": "PPTC (ذاكرة التخزين المؤقت للترجمة المستمرة)", + "SettingsTabSystemEnableFsIntegrityChecks": "التحقق من سلامة نظام الملفات", "SettingsTabSystemAudioBackend": "خلفية الصوت:", "SettingsTabSystemAudioBackendDummy": "زائف", "SettingsTabSystemAudioBackendOpenAL": "OpenAL", "SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSDL2": "SDL2", - "SettingsTabSystemHacks": "الاختراقات", + "SettingsTabSystemHacks": "هاكات", "SettingsTabSystemHacksNote": "قد يتسبب في عدم الاستقرار", "SettingsTabSystemExpandDramSize": "استخدام تخطيط الذاكرة البديل (المطورين)", "SettingsTabSystemIgnoreMissingServices": "تجاهل الخدمات المفقودة", "SettingsTabGraphics": "الرسومات", - "SettingsTabGraphicsAPI": "الرسومات API", + "SettingsTabGraphicsAPI": "API الرسومات ", "SettingsTabGraphicsEnableShaderCache": "تفعيل ذاكرة المظللات المؤقتة", - "SettingsTabGraphicsAnisotropicFiltering": "تصفية متباين الخواص:", + "SettingsTabGraphicsAnisotropicFiltering": "تصفية:", "SettingsTabGraphicsAnisotropicFilteringAuto": "تلقائي", "SettingsTabGraphicsAnisotropicFiltering2x": "2x", "SettingsTabGraphicsAnisotropicFiltering4x": "4×", @@ -152,7 +157,7 @@ "SettingsTabGraphicsAnisotropicFiltering16x": "16x", "SettingsTabGraphicsResolutionScale": "مقياس الدقة", "SettingsTabGraphicsResolutionScaleCustom": "مخصص (لا ينصح به)", - "SettingsTabGraphicsResolutionScaleNative": "الأصل (720p/1080p)", + "SettingsTabGraphicsResolutionScaleNative": "الأصل ‫(720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (لا ينصح به)", @@ -162,11 +167,11 @@ "SettingsTabGraphicsAspectRatio16x10": "16:10", "SettingsTabGraphicsAspectRatio21x9": "21:9", "SettingsTabGraphicsAspectRatio32x9": "32:9", - "SettingsTabGraphicsAspectRatioStretch": "تمدد لتلائم حجم النافذة", + "SettingsTabGraphicsAspectRatioStretch": "تمديد لتناسب النافذة", "SettingsTabGraphicsDeveloperOptions": "خيارات المطور", - "SettingsTabGraphicsShaderDumpPath": "مسار تفريغ الرسومات:", - "SettingsTabLogging": "التسجيل", - "SettingsTabLoggingLogging": "التسجيل", + "SettingsTabGraphicsShaderDumpPath": "مسار تفريغ المظللات:", + "SettingsTabLogging": "تسجيل", + "SettingsTabLoggingLogging": "تسجيل", "SettingsTabLoggingEnableLoggingToFile": "تفعيل التسجيل إلى ملف", "SettingsTabLoggingEnableStubLogs": "تفعيل سجلات الـStub", "SettingsTabLoggingEnableInfoLogs": "تفعيل سجلات المعلومات", @@ -174,8 +179,8 @@ "SettingsTabLoggingEnableErrorLogs": "تفعيل سجلات الأخطاء", "SettingsTabLoggingEnableTraceLogs": "تفعيل سجلات التتبع", "SettingsTabLoggingEnableGuestLogs": "تفعيل سجلات الضيوف", - "SettingsTabLoggingEnableFsAccessLogs": "تمكين سجلات الوصول لـ Fs", - "SettingsTabLoggingFsGlobalAccessLogMode": "وضع سجل وصول عالمي Fs :", + "SettingsTabLoggingEnableFsAccessLogs": "تمكين سجلات الوصول إلى نظام الملفات", + "SettingsTabLoggingFsGlobalAccessLogMode": "وضع سجل الوصول العالمي لنظام الملفات:", "SettingsTabLoggingDeveloperOptions": "خيارات المطور", "SettingsTabLoggingDeveloperOptionsNote": "تحذير: سوف يقلل من الأداء", "SettingsTabLoggingGraphicsBackendLogLevel": "مستوى سجل خلفية الرسومات:", @@ -185,15 +190,15 @@ "SettingsTabLoggingGraphicsBackendLogLevelAll": "الكل", "SettingsTabLoggingEnableDebugLogs": "تمكين سجلات التصحيح", "SettingsTabInput": "الإدخال", - "SettingsTabInputEnableDockedMode": "مركب بالمنصة", - "SettingsTabInputDirectKeyboardAccess": "الوصول المباشر إلى لوحة المفاتيح", + "SettingsTabInputEnableDockedMode": "تركيب بالمنصة", + "SettingsTabInputDirectKeyboardAccess": "الوصول المباشر للوحة المفاتيح", "SettingsButtonSave": "حفظ", "SettingsButtonClose": "إغلاق", "SettingsButtonOk": "موافق", "SettingsButtonCancel": "إلغاء", "SettingsButtonApply": "تطبيق", "ControllerSettingsPlayer": "اللاعب", - "ControllerSettingsPlayer1": "اللاعب ١", + "ControllerSettingsPlayer1": "اللاعب 1", "ControllerSettingsPlayer2": "اللاعب 2", "ControllerSettingsPlayer3": "اللاعب 3", "ControllerSettingsPlayer4": "اللاعب 4", @@ -205,12 +210,12 @@ "ControllerSettingsInputDevice": "جهاز الإدخال", "ControllerSettingsRefresh": "تحديث", "ControllerSettingsDeviceDisabled": "معطل", - "ControllerSettingsControllerType": "نوع ذراع التحكم", + "ControllerSettingsControllerType": "نوع وحدة التحكم", "ControllerSettingsControllerTypeHandheld": "محمول", - "ControllerSettingsControllerTypeProController": "Pro Controller", - "ControllerSettingsControllerTypeJoyConPair": "اقتران جوي كون", - "ControllerSettingsControllerTypeJoyConLeft": "يسار جوي كون", - "ControllerSettingsControllerTypeJoyConRight": "يمين جوي كون", + "ControllerSettingsControllerTypeProController": "وحدة تحكم برو", + "ControllerSettingsControllerTypeJoyConPair": "زوج جوي كون", + "ControllerSettingsControllerTypeJoyConLeft": "جوي كون اليسار ", + "ControllerSettingsControllerTypeJoyConRight": " جوي كون اليمين", "ControllerSettingsProfile": "الملف الشخصي", "ControllerSettingsProfileDefault": "افتراضي", "ControllerSettingsLoad": "تحميل", @@ -223,13 +228,13 @@ "ControllerSettingsButtonY": "Y", "ControllerSettingsButtonPlus": "+", "ControllerSettingsButtonMinus": "-", - "ControllerSettingsDPad": "لوحة الاتجاه", + "ControllerSettingsDPad": "أسهم الاتجاهات", "ControllerSettingsDPadUp": "اعلى", "ControllerSettingsDPadDown": "أسفل", "ControllerSettingsDPadLeft": "يسار", "ControllerSettingsDPadRight": "يمين", "ControllerSettingsStickButton": "زر", - "ControllerSettingsStickUp": "اعلى", + "ControllerSettingsStickUp": "فوق", "ControllerSettingsStickDown": "أسفل", "ControllerSettingsStickLeft": "يسار", "ControllerSettingsStickRight": "يمين", @@ -239,11 +244,11 @@ "ControllerSettingsStickDeadzone": "المنطقة الميتة:", "ControllerSettingsLStick": "العصا اليسرى", "ControllerSettingsRStick": "العصا اليمنى", - "ControllerSettingsTriggersLeft": "المحفزات اليسرى", - "ControllerSettingsTriggersRight": "المحفزات اليمني", - "ControllerSettingsTriggersButtonsLeft": "أزرار التحفيز اليسرى", - "ControllerSettingsTriggersButtonsRight": "أزرار التحفيز اليمنى", - "ControllerSettingsTriggers": "المحفزات", + "ControllerSettingsTriggersLeft": "الأزندة اليسرى", + "ControllerSettingsTriggersRight": "الأزندة اليمني", + "ControllerSettingsTriggersButtonsLeft": "أزرار الزناد اليسرى", + "ControllerSettingsTriggersButtonsRight": "أزرار الزناد اليمنى", + "ControllerSettingsTriggers": "أزندة", "ControllerSettingsTriggerL": "L", "ControllerSettingsTriggerR": "R", "ControllerSettingsTriggerZL": "ZL", @@ -258,27 +263,128 @@ "ControllerSettingsTriggerThreshold": "قوة التحفيز:", "ControllerSettingsMotion": "الحركة", "ControllerSettingsMotionUseCemuhookCompatibleMotion": "استخدام الحركة المتوافقة مع CemuHook", - "ControllerSettingsMotionControllerSlot": "خانة ذراع التحكم:", + "ControllerSettingsMotionControllerSlot": "خانة وحدة التحكم:", "ControllerSettingsMotionMirrorInput": "إعادة الإدخال", - "ControllerSettingsMotionRightJoyConSlot": "خانة اليمين JoyCon :", + "ControllerSettingsMotionRightJoyConSlot": "خانة جويكون اليمين :", "ControllerSettingsMotionServerHost": "مضيف الخادم:", - "ControllerSettingsMotionGyroSensitivity": "حساسية الغيرو:", - "ControllerSettingsMotionGyroDeadzone": "منطقة الغيرو الميتة:", + "ControllerSettingsMotionGyroSensitivity": "حساسية مستشعر الحركة:", + "ControllerSettingsMotionGyroDeadzone": "منطقة مستشعر الحركة الميتة:", "ControllerSettingsSave": "حفظ", "ControllerSettingsClose": "إغلاق", + "KeyUnknown": "مجهول", + "KeyShiftLeft": "زر ‫Shift الأيسر", + "KeyShiftRight": "زر ‫Shift الأيمن", + "KeyControlLeft": "زر ‫Ctrl الأيسر", + "KeyMacControlLeft": "زر ⌃ الأيسر", + "KeyControlRight": "زر ‫Ctrl الأيمن", + "KeyMacControlRight": "زر ⌃ الأيمن", + "KeyAltLeft": "زر ‫Alt الأيسر", + "KeyMacAltLeft": "زر ⌥ الأيسر", + "KeyAltRight": "زر ‫Alt الأيمن", + "KeyMacAltRight": "زر ⌥ الأيمن", + "KeyWinLeft": "زر ⊞ الأيسر", + "KeyMacWinLeft": "زر ⌘ الأيسر", + "KeyWinRight": "زر ⊞ الأيمن", + "KeyMacWinRight": "زر ⌘ الأيمن", + "KeyMenu": "زر القائمة", + "KeyUp": "فوق", + "KeyDown": "اسفل", + "KeyLeft": "يسار", + "KeyRight": "يمين", + "KeyEnter": "مفتاح الإدخال", + "KeyEscape": "زر ‫Escape", + "KeySpace": "مسافة", + "KeyTab": "زر ‫Tab", + "KeyBackSpace": "زر المسح للخلف", + "KeyInsert": "زر Insert", + "KeyDelete": "زر الحذف", + "KeyPageUp": "زر ‫Page Up", + "KeyPageDown": "زر ‫Page Down", + "KeyHome": "زر ‫Home", + "KeyEnd": "زر ‫End", + "KeyCapsLock": "زر الحروف الكبيرة", + "KeyScrollLock": "زر ‫Scroll Lock", + "KeyPrintScreen": "زر ‫Print Screen", + "KeyPause": "زر Pause", + "KeyNumLock": "زر Num Lock", + "KeyClear": "زر Clear", + "KeyKeypad0": "لوحة الأرقام 0", + "KeyKeypad1": "لوحة الأرقام 1", + "KeyKeypad2": "لوحة الأرقام 2", + "KeyKeypad3": "لوحة الأرقام 3", + "KeyKeypad4": "لوحة الأرقام 4", + "KeyKeypad5": "لوحة الأرقام 5", + "KeyKeypad6": "لوحة الأرقام 6", + "KeyKeypad7": "لوحة الأرقام 7", + "KeyKeypad8": "لوحة الأرقام 8", + "KeyKeypad9": "لوحة الأرقام 9", + "KeyKeypadDivide": "لوحة الأرقام علامة القسمة", + "KeyKeypadMultiply": "لوحة الأرقام علامة الضرب", + "KeyKeypadSubtract": "لوحة الأرقام علامة الطرح\n", + "KeyKeypadAdd": "لوحة الأرقام علامة الزائد", + "KeyKeypadDecimal": "لوحة الأرقام الفاصلة العشرية", + "KeyKeypadEnter": "لوحة الأرقام زر الإدخال", + "KeyNumber0": "٠", + "KeyNumber1": "١", + "KeyNumber2": "٢", + "KeyNumber3": "٣", + "KeyNumber4": "٤", + "KeyNumber5": "٥", + "KeyNumber6": "٦", + "KeyNumber7": "٧", + "KeyNumber8": "٨", + "KeyNumber9": "٩", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "غير مرتبط", + "GamepadLeftStick": "زر عصا التحكم اليسرى", + "GamepadRightStick": "زر عصا التحكم اليمنى", + "GamepadLeftShoulder": "زر الكتف الأيسر‫ L", + "GamepadRightShoulder": "زر الكتف الأيمن‫ R", + "GamepadLeftTrigger": "زر الزناد الأيسر‫ (ZL)", + "GamepadRightTrigger": "زر الزناد الأيمن‫ (ZR)", + "GamepadDpadUp": "فوق", + "GamepadDpadDown": "اسفل", + "GamepadDpadLeft": "يسار", + "GamepadDpadRight": "يمين", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "دليل", + "GamepadMisc1": "متنوع", + "GamepadPaddle1": "Paddle 1", + "GamepadPaddle2": "Paddle 2", + "GamepadPaddle3": "Paddle 3", + "GamepadPaddle4": "Paddle 4", + "GamepadTouchpad": "لوحة اللمس", + "GamepadSingleLeftTrigger0": "زر الزناد الأيسر 0", + "GamepadSingleRightTrigger0": "زر الزناد الأيمن 0", + "GamepadSingleLeftTrigger1": "زر الزناد الأيسر 1", + "GamepadSingleRightTrigger1": "زر الزناد الأيمن 1", + "StickLeft": "عصا التحكم اليسرى", + "StickRight": "عصا التحكم اليمنى", "UserProfilesSelectedUserProfile": "الملف الشخصي المحدد للمستخدم:", "UserProfilesSaveProfileName": "حفظ اسم الملف الشخصي", "UserProfilesChangeProfileImage": "تغيير صورة الملف الشخصي", "UserProfilesAvailableUserProfiles": "الملفات الشخصية للمستخدم المتاحة:", - "UserProfilesAddNewProfile": "أنشئ ملف شخصي", + "UserProfilesAddNewProfile": "إنشاء ملف الشخصي", "UserProfilesDelete": "حذف", "UserProfilesClose": "إغلاق", "ProfileNameSelectionWatermark": "اختر اسم مستعار", "ProfileImageSelectionTitle": "تحديد صورة الملف الشخصي", "ProfileImageSelectionHeader": "اختر صورة الملف الشخصي", - "ProfileImageSelectionNote": "يمكنك استيراد صورة ملف تعريف مخصصة، أو تحديد صورة رمزية من البرامج الثابتة للنظام", + "ProfileImageSelectionNote": "يمكنك استيراد صورة ملف شخصي مخصصة، أو تحديد صورة رمزية من البرامج الثابتة للنظام", "ProfileImageSelectionImportImage": "استيراد ملف الصورة", - "ProfileImageSelectionSelectAvatar": "حدد الصورة الرمزية للبرنامج الثابت", + "ProfileImageSelectionSelectAvatar": "حدد الصورة الرمزية من البرنامج الثابتة", "InputDialogTitle": "حوار الإدخال", "InputDialogOk": "موافق", "InputDialogCancel": "إلغاء", @@ -289,13 +395,13 @@ "AvatarSetBackgroundColor": "تعيين لون الخلفية", "AvatarClose": "إغلاق", "ControllerSettingsLoadProfileToolTip": "تحميل الملف الشخصي", - "ControllerSettingsAddProfileToolTip": "إضافة ملف تعريف", - "ControllerSettingsRemoveProfileToolTip": "إزالة ملف التعريف", - "ControllerSettingsSaveProfileToolTip": "حفظ ملف التعريف", + "ControllerSettingsAddProfileToolTip": "إضافة ملف شخصي", + "ControllerSettingsRemoveProfileToolTip": "إزالة الملف الشخصي", + "ControllerSettingsSaveProfileToolTip": "حفظ الملف الشخصي", "MenuBarFileToolsTakeScreenshot": "أخذ لقطة للشاشة", "MenuBarFileToolsHideUi": "إخفاء واجهة المستخدم", "GameListContextMenuRunApplication": "تشغيل التطبيق", - "GameListContextMenuToggleFavorite": "تبديل المفضلة", + "GameListContextMenuToggleFavorite": "تعيين كمفضل", "GameListContextMenuToggleFavoriteToolTip": "تبديل الحالة المفضلة للعبة", "SettingsTabGeneralTheme": "السمة:", "SettingsTabGeneralThemeDark": "داكن", @@ -304,44 +410,44 @@ "ControllerSettingsRumble": "الاهتزاز", "ControllerSettingsRumbleStrongMultiplier": "مضاعف اهتزاز قوي", "ControllerSettingsRumbleWeakMultiplier": "مضاعف اهتزاز ضعيف", - "DialogMessageSaveNotAvailableMessage": "لا يوجد حفظ لـ {0} [{1:x16}]", - "DialogMessageSaveNotAvailableCreateSaveMessage": "هل ترغب في إنشاء حفظ لهذه اللعبة؟", - "DialogConfirmationTitle": "Ryujinx - تأكيد", - "DialogUpdaterTitle": "تحديث Ryujinx", - "DialogErrorTitle": "Ryujinx - خطأ", - "DialogWarningTitle": "Ryujinx - تحذير", - "DialogExitTitle": "Ryujinx - الخروج", - "DialogErrorMessage": "واجه Ryujinx خطأ", - "DialogExitMessage": "هل أنت متأكد من أنك تريد إغلاق Ryujinx؟", + "DialogMessageSaveNotAvailableMessage": "لا توجد بيانات الحفظ لـ {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "هل ترغب في إنشاء بيانات الحفظ لهذه اللعبة؟", + "DialogConfirmationTitle": "ريوجينكس - تأكيد", + "DialogUpdaterTitle": "ريوجينكس - المحدث", + "DialogErrorTitle": "ريوجينكس - خطأ", + "DialogWarningTitle": "ريوجينكس - تحذير", + "DialogExitTitle": "ريوجينكس - الخروج", + "DialogErrorMessage": "واجه ريوجينكس خطأ", + "DialogExitMessage": "هل أنت متأكد من أنك تريد إغلاق ريوجينكس؟", "DialogExitSubMessage": "سيتم فقدان كافة البيانات غير المحفوظة!", - "DialogMessageCreateSaveErrorMessage": "حدث خطأ أثناء إنشاء المحفوظة المحددة: {0}", - "DialogMessageFindSaveErrorMessage": "حدث خطأ أثناء البحث عن البيانات المحفوظة المحددة: {0}", + "DialogMessageCreateSaveErrorMessage": "حدث خطأ أثناء إنشاء بيانات الحفظ المحددة: {0}", + "DialogMessageFindSaveErrorMessage": "حدث خطأ أثناء البحث عن بيانات الحفظ المحددة: {0}", "FolderDialogExtractTitle": "اختر المجلد الذي تريد الاستخراج إليه", "DialogNcaExtractionMessage": "استخراج قسم {0} من {1}...", - "DialogNcaExtractionTitle": "Ryujinx - مستخرج قسم NCA", - "DialogNcaExtractionMainNcaNotFoundErrorMessage": "فشل الاستخراج. لم يكن NCA الرئيسي موجودًا في الملف المحدد.", - "DialogNcaExtractionCheckLogErrorMessage": "فشل الاستخراج. اقرأ ملف السجل لمزيد من المعلومات.", + "DialogNcaExtractionTitle": "ريوجينكس - مستخرج قسم NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "فشل الاستخراج. لم يكن NCA الرئيسي موجودا في الملف المحدد.", + "DialogNcaExtractionCheckLogErrorMessage": "فشل الاستخراج. اقرأ ملف التسجيل لمزيد من المعلومات.", "DialogNcaExtractionSuccessMessage": "تم الاستخراج بنجاح.", - "DialogUpdaterConvertFailedMessage": "فشل تحويل إصدار Ryujinx الحالي.", + "DialogUpdaterConvertFailedMessage": "فشل تحويل إصدار ريوجينكس الحالي.", "DialogUpdaterCancelUpdateMessage": "إلغاء التحديث", - "DialogUpdaterAlreadyOnLatestVersionMessage": "أنت تستخدم بالفعل أحدث إصدار من Ryujinx!", - "DialogUpdaterFailedToGetVersionMessage": "حدث خطأ أثناء محاولة الحصول على معلومات الإصدار من إصدار GitHub. يمكن أن يحدث هذا إذا تم تجميع إصدار جديد بواسطة GitHub Actions. جرب مجددا بعد دقائق.", - "DialogUpdaterConvertFailedGithubMessage": "فشل تحويل إصدار Ryujinx المستلم من إصدار Github.", - "DialogUpdaterDownloadingMessage": "تحميل التحديث...", - "DialogUpdaterExtractionMessage": "استخراج التحديث...", + "DialogUpdaterAlreadyOnLatestVersionMessage": "أنت تستخدم بالفعل أحدث إصدار من ريوجينكس!", + "DialogUpdaterFailedToGetVersionMessage": "حدث خطأ أثناء محاولة الحصول على معلومات الإصدار من إصدار غيت هاب. يمكن أن يحدث هذا إذا تم تجميع إصدار جديد بواسطة إجراءات غيت هاب. جرب مجددا بعد دقائق.", + "DialogUpdaterConvertFailedGithubMessage": "فشل تحويل إصدار ريوجينكس المستلم من إصدار غيت هاب.", + "DialogUpdaterDownloadingMessage": "جاري تنزيل التحديث...", + "DialogUpdaterExtractionMessage": "جاري استخراج التحديث...", "DialogUpdaterRenamingMessage": "إعادة تسمية التحديث...", "DialogUpdaterAddingFilesMessage": "إضافة تحديث جديد...", "DialogUpdaterCompleteMessage": "اكتمل التحديث", - "DialogUpdaterRestartMessage": "هل تريد إعادة تشغيل Ryujinx الآن؟", + "DialogUpdaterRestartMessage": "هل تريد إعادة تشغيل ريوجينكس الآن؟", "DialogUpdaterNoInternetMessage": "أنت غير متصل بالإنترنت.", "DialogUpdaterNoInternetSubMessage": "يرجى التحقق من أن لديك اتصال إنترنت فعال!", - "DialogUpdaterDirtyBuildMessage": "لا يمكنك تحديث البناء القذر من Ryujinx!", - "DialogUpdaterDirtyBuildSubMessage": "الرجاء تحميل Ryujinx على https://ryujinx.org/ إذا كنت تبحث عن إصدار مدعوم.", + "DialogUpdaterDirtyBuildMessage": "لا يمكنك تحديث نسخة القذرة من ريوجينكس!", + "DialogUpdaterDirtyBuildSubMessage": "الرجاء تحميل ريوجينكس من https://ryujinx.org إذا كنت تبحث عن إصدار مدعوم.", "DialogRestartRequiredMessage": "يتطلب إعادة التشغيل", "DialogThemeRestartMessage": "تم حفظ السمة. إعادة التشغيل مطلوبة لتطبيق السمة.", "DialogThemeRestartSubMessage": "هل تريد إعادة التشغيل", "DialogFirmwareInstallEmbeddedMessage": "هل ترغب في تثبيت البرنامج الثابت المدمج في هذه اللعبة؟ (البرنامج الثابت {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "لم يتم العثور على أي برنامج ثابت مثبت ولكن Ryujinx كان قادراً على تثبيت البرنامج الثابت {0} من اللعبة المقدمة.\nسيبدأ المحاكي الآن.", + "DialogFirmwareInstallEmbeddedSuccessMessage": "لم يتم العثور على أي برنامج ثابت مثبت ولكن ريوجينكس كان قادرا على تثبيت البرنامج الثابت {0} من اللعبة المقدمة.\nسيبدأ المحاكي الآن.", "DialogFirmwareNoFirmwareInstalledMessage": "لا يوجد برنامج ثابت مثبت", "DialogFirmwareInstalledMessage": "تم تثبيت البرنامج الثابت {0}", "DialogInstallFileTypesSuccessMessage": "تم تثبيت أنواع الملفات بنجاح!", @@ -349,38 +455,38 @@ "DialogUninstallFileTypesSuccessMessage": "تم إلغاء تثبيت أنواع الملفات بنجاح!", "DialogUninstallFileTypesErrorMessage": "فشل إلغاء تثبيت أنواع الملفات.", "DialogOpenSettingsWindowLabel": "فتح نافذة الإعدادات", - "DialogControllerAppletTitle": "برنامج التحكم", + "DialogControllerAppletTitle": "تطبيق وحدة التحكم المصغر", "DialogMessageDialogErrorExceptionMessage": "خطأ في عرض مربع حوار الرسالة: {0}", "DialogSoftwareKeyboardErrorExceptionMessage": "خطأ في عرض لوحة مفاتيح البرامج: {0}", - "DialogErrorAppletErrorExceptionMessage": "Error displaying ErrorApplet Dialog: {0}", + "DialogErrorAppletErrorExceptionMessage": "خطأ في عرض مربع حوار خطأ التطبيق المصغر: {0}", "DialogUserErrorDialogMessage": "{0}: {1}", "DialogUserErrorDialogInfoMessage": "لمزيد من المعلومات حول كيفية إصلاح هذا الخطأ، اتبع دليل الإعداد الخاص بنا.", "DialogUserErrorDialogTitle": "خطأ ريوجينكس ({0})", "DialogAmiiboApiTitle": "أميبو API", - "DialogAmiiboApiFailFetchMessage": "حدث خطأ أثناء جلب المعلومات من واجهة برمجة التطبيقات.", - "DialogAmiiboApiConnectErrorMessage": "غير قادر على الاتصال بخادم API Amiibo. قد تكون الخدمة متوقفة أو قد تحتاج إلى التحقق من اتصال الإنترنت الخاص بك على الإنترنت.", + "DialogAmiiboApiFailFetchMessage": "حدث خطأ أثناء جلب المعلومات من API.", + "DialogAmiiboApiConnectErrorMessage": "غير قادر على الاتصال بخادم API أميبو. قد تكون الخدمة معطلة أو قد تحتاج إلى التحقق من اتصالك بالإنترنت.", "DialogProfileInvalidProfileErrorMessage": "الملف الشخصي {0} غير متوافق مع نظام تكوين الإدخال الحالي.", "DialogProfileDefaultProfileOverwriteErrorMessage": "لا يمكن الكتابة فوق الملف الشخصي الافتراضي", - "DialogProfileDeleteProfileTitle": "حذف ملف التعريف", + "DialogProfileDeleteProfileTitle": "حذف الملف الشخصي", "DialogProfileDeleteProfileMessage": "هذا الإجراء لا رجعة فيه، هل أنت متأكد من أنك تريد المتابعة؟", "DialogWarning": "تحذير", - "DialogPPTCDeletionMessage": "أنت على وشك الإنتظار لإعادة بناء PTC على التمهيد التالي :\n\n{0}\n\nهل أنت متأكد من أنك تريد المتابعة؟", - "DialogPPTCDeletionErrorMessage": "خطأ في إزالة ذاكرة التخزين المؤقت PPTC في {0}: {1}", - "DialogShaderDeletionMessage": "أنت على وشك حذف ذاكرة التخزين المؤقت لـ Shader من أجل:\n\n{0}\n\nهل انت متأكد انك تريد المتابعة؟", - "DialogShaderDeletionErrorMessage": "Error purging Shader cache at {0}: {1}", - "DialogRyujinxErrorMessage": "واجه Ryujinx خطأ", + "DialogPPTCDeletionMessage": "أنت على وشك الإنتظار لإعادة بناء ذاكرة التخزين المؤقت للترجمة المستمرة (PPTC) عند الإقلاع التالي لـ:\n\n{0}\n\nأمتأكد من رغبتك في المتابعة؟", + "DialogPPTCDeletionErrorMessage": "خطأ خلال تنظيف ذاكرة التخزين المؤقت للترجمة المستمرة (PPTC) في {0}: {1}", + "DialogShaderDeletionMessage": "أنت على وشك حذف ذاكرة المظللات المؤقتة ل:\n\n{0}\n\nهل انت متأكد انك تريد المتابعة؟", + "DialogShaderDeletionErrorMessage": "حدث خطأ أثناء تنظيف ذاكرة المظللات المؤقتة في {0}: {1}", + "DialogRyujinxErrorMessage": "واجه ريوجينكس خطأ", "DialogInvalidTitleIdErrorMessage": "خطأ في واجهة المستخدم: اللعبة المحددة لم يكن لديها معرف عنوان صالح", - "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "لم يتم العثور على فريموير للنظام صالح في {0}.", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "لم يتم العثور على برنامج ثابت للنظام صالح في {0}.", "DialogFirmwareInstallerFirmwareInstallTitle": "تثبيت البرنامج الثابت {0}", "DialogFirmwareInstallerFirmwareInstallMessage": "سيتم تثبيت إصدار النظام {0}.", "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nهذا سيحل محل إصدار النظام الحالي {0}.", "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\nهل تريد المتابعة؟", "DialogFirmwareInstallerFirmwareInstallWaitMessage": "تثبيت البرنامج الثابت...", "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "تم تثبيت إصدار النظام {0} بنجاح.", - "DialogUserProfileDeletionWarningMessage": "لن تكون هناك ملفات تعريف أخرى لفتحها إذا تم حذف الملف الشخصي المحدد", + "DialogUserProfileDeletionWarningMessage": "لن تكون هناك ملفات الشخصية أخرى لفتحها إذا تم حذف الملف الشخصي المحدد", "DialogUserProfileDeletionConfirmMessage": "هل تريد حذف الملف الشخصي المحدد", - "DialogUserProfileUnsavedChangesTitle": "تحذير - التغييرات غير المحفوظة", - "DialogUserProfileUnsavedChangesMessage": "لقد قمت بإجراء تغييرات على ملف تعريف المستخدم هذا ولم يتم حفظها.", + "DialogUserProfileUnsavedChangesTitle": "تحذير - التغييرات غير محفوظة", + "DialogUserProfileUnsavedChangesMessage": "لقد قمت بإجراء تغييرات على الملف الشخصي لهذا المستخدم هذا ولم يتم حفظها.", "DialogUserProfileUnsavedChangesSubMessage": "هل تريد تجاهل التغييرات؟", "DialogControllerSettingsModifiedConfirmMessage": "تم تحديث إعدادات وحدة التحكم الحالية.", "DialogControllerSettingsModifiedConfirmSubMessage": "هل تريد الحفظ ؟", @@ -388,29 +494,29 @@ "DialogModAlreadyExistsMessage": "التعديل موجود بالفعل", "DialogModInvalidMessage": "المجلد المحدد لا يحتوي على تعديل!", "DialogModDeleteNoParentMessage": "فشل الحذف: لم يمكن العثور على المجلد الرئيسي للتعديل\"{0}\"!", - "DialogDlcNoDlcErrorMessage": "الملف المحدد لا يحتوي على DLC للعنوان المحدد!", + "DialogDlcNoDlcErrorMessage": "الملف المحدد لا يحتوي على محتوى إضافي للعنوان المحدد!", "DialogPerformanceCheckLoggingEnabledMessage": "لقد تم تمكين تسجيل التتبع، والذي تم تصميمه ليتم استخدامه من قبل المطورين فقط.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "للحصول على الأداء الأمثل، يوصى بتعطيل تسجيل التتبع. هل ترغب في تعطيل تسجيل التتبع الآن؟", - "DialogPerformanceCheckShaderDumpEnabledMessage": "لقد قمت بتمكين تفريغ التظليل، والذي تم تصميمه ليستخدمه المطورون فقط.", - "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "For optimal performance, it's recommended to disable shader dumping. Would you like to disable shader dumping now?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "لقد قمت بتمكين تفريغ المظللات، والذي تم تصميمه ليستخدمه المطورون فقط.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "للحصول على الأداء الأمثل، يوصى بتعطيل تفريغ المظللات. هل ترغب في تعطيل تفريغ المظللات الآن؟", "DialogLoadAppGameAlreadyLoadedMessage": "تم تحميل لعبة بالفعل", "DialogLoadAppGameAlreadyLoadedSubMessage": "الرجاء إيقاف المحاكاة أو إغلاق المحاكي قبل بدء لعبة أخرى.", - "DialogUpdateAddUpdateErrorMessage": "الملف المحدد لا يحتوي على تحديث للملف المحدد!", - "DialogSettingsBackendThreadingWarningTitle": "Warning - Backend Threading", - "DialogSettingsBackendThreadingWarningMessage": "Ryujinx must be restarted after changing this option for it to apply fully. Depending on your platform, you may need to manually disable your driver's own multithreading when using Ryujinx's.", + "DialogUpdateAddUpdateErrorMessage": "الملف المحدد لا يحتوي على تحديث للعنوان المحدد!", + "DialogSettingsBackendThreadingWarningTitle": "تحذير - خلفية متعددة المسارات", + "DialogSettingsBackendThreadingWarningMessage": "يجب إعادة تشغيل ريوجينكس بعد تغيير هذا الخيار حتى يتم تطبيقه بالكامل. اعتمادا على النظام الأساسي الخاص بك، قد تحتاج إلى تعطيل تعدد المسارات الخاص ببرنامج الرسومات التشغيل الخاص بك يدويًا عند استخدام الخاص بريوجينكس.", "DialogModManagerDeletionWarningMessage": "أنت على وشك حذف التعديل: {0}\n\nهل انت متأكد انك تريد المتابعة؟", "DialogModManagerDeletionAllWarningMessage": "أنت على وشك حذف كافة التعديلات لهذا العنوان.\n\nهل انت متأكد انك تريد المتابعة؟", "SettingsTabGraphicsFeaturesOptions": "المميزات", - "SettingsTabGraphicsBackendMultithreading": "Graphics Backend Multithreading:", + "SettingsTabGraphicsBackendMultithreading": "تعدد المسارات لخلفية الرسومات:", "CommonAuto": "تلقائي", - "CommonOff": "إيقاف", + "CommonOff": "معطل", "CommonOn": "تشغيل", "InputDialogYes": "نعم", "InputDialogNo": "لا", "DialogProfileInvalidProfileNameErrorMessage": "يحتوي اسم الملف على أحرف غير صالحة. يرجى المحاولة مرة أخرى.", "MenuBarOptionsPauseEmulation": "إيقاف مؤقت", "MenuBarOptionsResumeEmulation": "استئناف", - "AboutUrlTooltipMessage": "انقر لفتح موقع Ryujinx في متصفحك الافتراضي.", + "AboutUrlTooltipMessage": "انقر لفتح موقع ريوجينكس في متصفحك الافتراضي.", "AboutDisclaimerMessage": "ريوجينكس لا ينتمي إلى نينتندو™،\nأو أي من شركائها بأي شكل من الأشكال.", "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) يتم \nاستخدامه في محاكاة أمبيو لدينا.", "AboutPatreonUrlTooltipMessage": "انقر لفتح صفحة ريوجينكس في باتريون في متصفحك الافتراضي.", @@ -419,18 +525,18 @@ "AboutTwitterUrlTooltipMessage": "انقر لفتح صفحة ريوجينكس في تويتر في متصفحك الافتراضي.", "AboutRyujinxAboutTitle": "حول:", "AboutRyujinxAboutContent": "ريوجينكس هو محاكي لجهاز نينتندو سويتش™.\nمن فضلك ادعمنا على باتريون.\nاحصل على آخر الأخبار على تويتر أو ديسكورد.\nيمكن للمطورين المهتمين بالمساهمة معرفة المزيد على غيت هاب أو ديسكورد.", - "AboutRyujinxMaintainersTitle": "تم إصلاحها بواسطة:", + "AboutRyujinxMaintainersTitle": "تتم صيانته بواسطة:", "AboutRyujinxMaintainersContentTooltipMessage": "انقر لفتح صفحة المساهمين في متصفحك الافتراضي.", "AboutRyujinxSupprtersTitle": "مدعوم على باتريون بواسطة:", "AmiiboSeriesLabel": "مجموعة أميبو", "AmiiboCharacterLabel": "شخصية", "AmiiboScanButtonLabel": "فحصه", "AmiiboOptionsShowAllLabel": "إظهار كل أميبو", - "AmiiboOptionsUsRandomTagLabel": "Hack: Use Random tag Uuid", + "AmiiboOptionsUsRandomTagLabel": "هاك: استخدم علامة Uuid عشوائية ", "DlcManagerTableHeadingEnabledLabel": "مفعل", "DlcManagerTableHeadingTitleIdLabel": "معرف العنوان", "DlcManagerTableHeadingContainerPathLabel": "مسار الحاوية", - "DlcManagerTableHeadingFullPathLabel": "المسار كاملاً", + "DlcManagerTableHeadingFullPathLabel": "المسار كاملا", "DlcManagerRemoveAllButton": "حذف الكل", "DlcManagerEnableAllButton": "تشغيل الكل", "DlcManagerDisableAllButton": "تعطيل الكل", @@ -440,100 +546,100 @@ "CommonSort": "فرز", "CommonShowNames": "عرض الأسماء", "CommonFavorite": "المفضلة", - "OrderAscending": "ترتيب تصاعدي", - "OrderDescending": "ترتيب تنازلي", + "OrderAscending": "تصاعدي", + "OrderDescending": "تنازلي", "SettingsTabGraphicsFeatures": "الميزات والتحسينات", "ErrorWindowTitle": "نافذة الخطأ", - "ToggleDiscordTooltip": "اختر ما إذا كنت تريد عرض ريوجينكس في نشاط ديسكورد \"يتم تشغيله حاليًا\" أم لا", - "AddGameDirBoxTooltip": "أدخل دليل اللعبة لإضافته إلى القائمة", - "AddGameDirTooltip": "إضافة دليل اللعبة إلى القائمة", - "RemoveGameDirTooltip": "إزالة دليل اللعبة المحدد", + "ToggleDiscordTooltip": "اختر ما إذا كنت تريد عرض ريوجينكس في نشاط ديسكورد \"يتم تشغيله حاليا\" أم لا", + "AddGameDirBoxTooltip": "أدخل مجلد اللعبة لإضافته إلى القائمة", + "AddGameDirTooltip": "إضافة مجلد اللعبة إلى القائمة", + "RemoveGameDirTooltip": "إزالة مجلد اللعبة المحدد", "CustomThemeCheckTooltip": "استخدم سمة أفالونيا المخصصة لواجهة المستخدم الرسومية لتغيير مظهر قوائم المحاكي", "CustomThemePathTooltip": "مسار سمة واجهة المستخدم المخصصة", "CustomThemeBrowseTooltip": "تصفح للحصول على سمة واجهة المستخدم المخصصة", - "DockModeToggleTooltip": "يجعل وضع الإرساء النظام الذي تمت محاكاته بمثابة جهاز نينتندو سويتش الذي تم إرساءه. يؤدي هذا إلى تحسين الدقة الرسومية في معظم الألعاب. على العكس من ذلك، سيؤدي تعطيل هذا إلى جعل النظام الذي تمت محاكاته يعمل كجهاز نينتندو سويتش محمول، مما يقلل من جودة الرسومات.\n\nقم بتكوين عناصر تحكم اللاعب 1 إذا كنت تخطط لاستخدام وضع الإرساء؛ قم بتكوين عناصر التحكم المحمولة إذا كنت تخطط لاستخدام الوضع المحمول.\n\nاتركه مشغل إذا لم تكن متأكدًا.", - "DirectKeyboardTooltip": "دعم الوصول المباشر إلى لوحة المفاتيح (HID). يوفر وصول الألعاب إلى لوحة المفاتيح الخاصة بك كجهاز لإدخال النص.\n\nيعمل فقط مع الألعاب التي تدعم استخدام لوحة المفاتيح في الأصل على أجهزة سويتش.\n\nاتركه معطلا إذا كنت غير متأكد.", - "DirectMouseTooltip": "دعم الوصول المباشر إلى لوحة المفاتيح (HID). يوفر وصول الألعاب إلى لوحة المفاتيح الخاصة بك كجهاز لإدخال النص.\n\nيعمل فقط مع الألعاب التي تدعم استخدام لوحة المفاتيح في الأصل على أجهزة سويتش.\n\nاتركه معطلا إذا كنت غير متأكد.", + "DockModeToggleTooltip": "يجعل وضع تركيب بالمنصة النظام الذي تمت محاكاته بمثابة جهاز نينتندو سويتش الذي تم تركيبه بالمنصة. يؤدي هذا إلى تحسين الدقة الرسومية في معظم الألعاب. على العكس من ذلك، سيؤدي تعطيل هذا إلى جعل النظام الذي تمت محاكاته يعمل كجهاز نينتندو سويتش محمول، مما يقلل من جودة الرسومات.\n\nقم بتكوين عناصر تحكم اللاعب 1 إذا كنت تخطط لاستخدام وضع تركيب بالمنصة؛ قم بتكوين عناصر التحكم المحمولة إذا كنت تخطط لاستخدام الوضع المحمول.\n\nاتركه مشغل إذا لم تكن متأكدا.", + "DirectKeyboardTooltip": "دعم الوصول المباشر للوحة المفاتيح (HID). يوفر وصول الألعاب إلى لوحة المفاتيح الخاصة بك كجهاز لإدخال النص.\n\nيعمل فقط مع الألعاب التي تدعم استخدام لوحة المفاتيح في الأصل على أجهزة سويتش.\n\nاتركه معطلا إذا كنت غير متأكد.", + "DirectMouseTooltip": "دعم الوصول المباشر للوحة المفاتيح (HID). يوفر وصول الألعاب إلى لوحة المفاتيح الخاصة بك كجهاز لإدخال النص.\n\nيعمل فقط مع الألعاب التي تدعم استخدام لوحة المفاتيح في الأصل على أجهزة سويتش.\n\nاتركه معطلا إذا كنت غير متأكد.", "RegionTooltip": "تغيير منطقة النظام", "LanguageTooltip": "تغيير لغة النظام", - "TimezoneTooltip": "تغيير المنطقة الزمنية للنظام", + "TimezoneTooltip": "تغيير النطاق الزمني للنظام", "TimeTooltip": "تغيير وقت النظام", - "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", - "PptcToggleTooltip": "Saves translated JIT functions so that they do not need to be translated every time the game loads.\n\nReduces stuttering and significantly speeds up boot times after the first boot of a game.\n\nLeave ON if unsure.", + "VSyncToggleTooltip": "محاكاة المزامنة العمودية للجهاز. في الأساس محدد الإطار لغالبية الألعاب؛ قد يؤدي تعطيله إلى تشغيل الألعاب بسرعة أعلى أو جعل شاشات التحميل تستغرق وقتا أطول أو تتعطل.\n\nيمكن تبديله داخل اللعبة باستخدام مفتاح التشغيل السريع الذي تفضله (F1 افتراضيا). نوصي بالقيام بذلك إذا كنت تخطط لتعطيله.\n\nاتركه ممكنا إذا لم تكن متأكدا.", + "PptcToggleTooltip": "يحفظ وظائف JIT المترجمة بحيث لا تحتاج إلى ترجمتها في كل مرة يتم فيها تحميل اللعبة.\n\nيقلل من التقطيع ويسرع بشكل ملحوظ أوقات التشغيل بعد التشغيل الأول للعبة.\n\nاتركه ممكنا إذا لم تكن متأكدا.", "FsIntegrityToggleTooltip": "يتحقق من وجود ملفات تالفة عند تشغيل لعبة ما، وإذا تم اكتشاف ملفات تالفة، فسيتم عرض خطأ تجزئة في السجل.\n\nليس له أي تأثير على الأداء ويهدف إلى المساعدة في استكشاف الأخطاء وإصلاحها.\n\nاتركه مفعلا إذا كنت غير متأكد.", - "AudioBackendTooltip": "Changes the backend used to render audio.\n\nSDL2 is the preferred one, while OpenAL and SoundIO are used as fallbacks. Dummy will have no sound.\n\nSet to SDL2 if unsure.", - "MemoryManagerTooltip": "Change how guest memory is mapped and accessed. Greatly affects emulated CPU performance.\n\nSet to HOST UNCHECKED if unsure.", + "AudioBackendTooltip": "يغير الواجهة الخلفية المستخدمة لتقديم الصوت.\n\nSDL2 هو الخيار المفضل، بينما يتم استخدام OpenAL وSoundIO كبديلين. زائف لن يكون لها صوت.\n\nاضبط على SDL2 إذا لم تكن متأكدا.", + "MemoryManagerTooltip": "تغيير كيفية تعيين ذاكرة الضيف والوصول إليها. يؤثر بشكل كبير على أداء وحدة المعالجة المركزية التي تمت محاكاتها.\n\nاضبط على المضيف غير محدد إذا لم تكن متأكدا.", "MemoryManagerSoftwareTooltip": "استخدام جدول الصفحات البرمجي لترجمة العناوين. أعلى دقة ولكن أبطأ أداء.", - "MemoryManagerHostTooltip": "Directly map memory in the host address space. Much faster JIT compilation and execution.", - "MemoryManagerUnsafeTooltip": "Directly map memory, but do not mask the address within the guest address space before access. Faster, but at the cost of safety. The guest application can access memory from anywhere in Ryujinx, so only run programs you trust with this mode.", - "UseHypervisorTooltip": "Use Hypervisor instead of JIT. Greatly improves performance when available, but can be unstable in its current state.", - "DRamTooltip": "Utilizes an alternative MemoryMode layout to mimic a Switch development model.\n\nThis is only useful for higher-resolution texture packs or 4k resolution mods. Does NOT improve performance.\n\nLeave OFF if unsure.", + "MemoryManagerHostTooltip": "تعيين الذاكرة مباشرة في مساحة عنوان المضيف. تجميع وتنفيذ JIT أسرع بكثير.", + "MemoryManagerUnsafeTooltip": "تعيين الذاكرة مباشرة، ولكن لا تخفي العنوان داخل مساحة عنوان الضيف قبل الوصول. أسرع، ولكن على حساب السلامة. يمكن لتطبيق الضيف الوصول إلى الذاكرة من أي مكان في ريوجينكس، لذا قم بتشغيل البرامج التي تثق بها فقط مع هذا الوضع.", + "UseHypervisorTooltip": "استخدم هايبرڤايزور بدلا من JIT. يعمل على تحسين الأداء بشكل كبير عند توفره، ولكنه قد يكون غير مستقر في حالته الحالية.", + "DRamTooltip": "يستخدم تخطيط وضع الذاكرة البديل لتقليد نموذج سويتش المطورين.\n\nيعد هذا مفيدا فقط لحزم النسيج عالية الدقة أو تعديلات دقة 4K. لا يحسن الأداء.\n\nاتركه معطلا إذا لم تكن متأكدا.", "IgnoreMissingServicesTooltip": "يتجاهل خدمات نظام هوريزون غير المنفذة. قد يساعد هذا في تجاوز الأعطال عند تشغيل ألعاب معينة.\n\nاتركه معطلا إذا كنت غير متأكد.", - "GraphicsBackendThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.", - "GalThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.", - "ShaderCacheToggleTooltip": "Saves a disk shader cache which reduces stuttering in subsequent runs.\n\nLeave ON if unsure.", - "ResolutionScaleTooltip": "يضاعف دقة عرض اللعبة.\n\nقد لا تعمل بعض الألعاب مع هذا وتبدو منقطة حتى عند زيادة الدقة؛ بالنسبة لهذه الألعاب، قد تحتاج إلى العثور على تعديلات تزيل الصقل أو تزيد من دقة العرض الداخلي. لاستخدام الأخير، من المحتمل أن ترغب في تحديد أصلي.\n\nيمكن تغيير هذا الخيار أثناء تشغيل اللعبة بالنقر فوق \"تطبيق\" أدناه؛ يمكنك ببساطة تحريك نافذة الإعدادات جانبًا والتجربة حتى تجد المظهر المفضل للعبة.\n\nضع في اعتبارك أن 4x مبالغة في أي إعداد تقريبًا.", + "GraphicsBackendThreadingTooltip": "ينفذ أوامر الواجهة الخلفية للرسومات على مسار ثاني.\n\nيعمل على تسريع عملية تجميع المظللات وتقليل التقطيع وتحسين الأداء على برامج تشغيل وحدة الرسوميات دون دعم المسارات المتعددة الخاصة بهم. أداء أفضل قليلا على برامج التشغيل ذات المسارات المتعددة.\n\nاضبط على تلقائي إذا لم تكن متأكدا.", + "GalThreadingTooltip": "ينفذ أوامر الواجهة الخلفية للرسومات على مسار ثاني.\n\nيعمل على تسريع عملية تجميع المظللات وتقليل التقطيع وتحسين الأداء على برامج تشغيل وحدة الرسوميات دون دعم المسارات المتعددة الخاصة بهم. أداء أفضل قليلا على برامج التشغيل ذات المسارات المتعددة.\n\nاضبط على تلقائي إذا لم تكن متأكدا.", + "ShaderCacheToggleTooltip": "يحفظ ذاكرة المظللات المؤقتة على القرص مما يقلل من التقطيع في عمليات التشغيل اللاحقة.\n\nاتركه مفعلا إذا لم تكن متأكدا.", + "ResolutionScaleTooltip": "يضاعف دقة عرض اللعبة.\n\nقد لا تعمل بعض الألعاب مع هذا وتبدو منقطة حتى عند زيادة الدقة؛ بالنسبة لهذه الألعاب، قد تحتاج إلى العثور على تعديلات تزيل تنعيم الحواف أو تزيد من دقة العرض الداخلي. لاستخدام الأخير، من المحتمل أن ترغب في تحديد أصلي.\n\nيمكن تغيير هذا الخيار أثناء تشغيل اللعبة بالنقر فوق \"تطبيق\" أدناه؛ يمكنك ببساطة تحريك نافذة الإعدادات جانبًا والتجربة حتى تجد المظهر المفضل للعبة.\n\nضع في اعتبارك أن 4x مبالغة في أي إعداد تقريبًا.", "ResolutionScaleEntryTooltip": "مقياس دقة النقطة العائمة، مثل 1.5. من المرجح أن تتسبب المقاييس غير المتكاملة في حدوث مشكلات أو تعطل.", - "AnisotropyTooltip": "مستوى تصفية متباين الخواص. اضبط على تلقائي لاستخدام القيمة التي تطلبها اللعبة.", - "AspectRatioTooltip": "يتم تطبيق نسبة العرض إلى الارتفاع على نافذة العارض.\n\nقم بتغيير هذا فقط إذا كنت تستخدم تعديل نسبة العرض إلى الارتفاع للعبتك، وإلا سيتم تمديد الرسومات.\n\nاتركه على 16:9 إذا لم تكن متأكدًا.", - "ShaderDumpPathTooltip": "Graphics Shaders Dump Path", + "AnisotropyTooltip": "مستوى تصفية. اضبط على تلقائي لاستخدام القيمة التي تطلبها اللعبة.", + "AspectRatioTooltip": "يتم تطبيق نسبة العرض إلى الارتفاع على نافذة العارض.\n\nقم بتغيير هذا فقط إذا كنت تستخدم تعديل نسبة العرض إلى الارتفاع للعبتك، وإلا سيتم تمديد الرسومات.\n\nاتركه16:9 إذا لم تكن متأكدا.", + "ShaderDumpPathTooltip": "مسار تفريغ المظللات", "FileLogTooltip": "حفظ تسجيل وحدة التحكم إلى ملف سجل على القرص. لا يؤثر على الأداء.", - "StubLogTooltip": "Prints stub log messages in the console. Does not affect performance.", - "InfoLogTooltip": "Prints info log messages in the console. Does not affect performance.", + "StubLogTooltip": "طباعة رسائل سجل stub في وحدة التحكم. لا يؤثر على الأداء.", + "InfoLogTooltip": "طباعة رسائل سجل المعلومات في وحدة التحكم. لا يؤثر على الأداء.", "WarnLogTooltip": "طباعة رسائل سجل التحذير في وحدة التحكم. لا يؤثر على الأداء.", "ErrorLogTooltip": "طباعة رسائل سجل الأخطاء في وحدة التحكم. لا يؤثر على الأداء.", "TraceLogTooltip": "طباعة رسائل سجل التتبع في وحدة التحكم. لا يؤثر على الأداء.", "GuestLogTooltip": "طباعة رسائل سجل الضيف في وحدة التحكم. لا يؤثر على الأداء.", "FileAccessLogTooltip": "طباعة رسائل سجل الوصول إلى الملفات في وحدة التحكم.", - "FSAccessLogModeTooltip": "Enables FS access log output to the console. Possible modes are 0-3", + "FSAccessLogModeTooltip": "تمكين إخراج سجل الوصول إلى نظام الملفات إلى وحدة التحكم. الأوضاع الممكنة هي 0-3", "DeveloperOptionTooltip": "استخدمه بعناية", "OpenGlLogLevel": "يتطلب تمكين مستويات السجل المناسبة", "DebugLogTooltip": "طباعة رسائل سجل التصحيح في وحدة التحكم.\n\nاستخدم هذا فقط إذا طلب منك أحد الموظفين تحديدًا ذلك، لأنه سيجعل من الصعب قراءة السجلات وسيؤدي إلى تدهور أداء المحاكي.", - "LoadApplicationFileTooltip": "افتح مستكشف الملفات لاختيار ملف متوافق مع Switch لتحميله", - "LoadApplicationFolderTooltip": "افتح مستكشف الملفات لاختيار تطبيق متوافق مع Switch للتحميل", - "OpenRyujinxFolderTooltip": "فتح مجلد نظام ملفات Ryujinx", + "LoadApplicationFileTooltip": "افتح مستكشف الملفات لاختيار ملف متوافق مع سويتش لتحميله", + "LoadApplicationFolderTooltip": "افتح مستكشف الملفات لاختيار تطبيق متوافق مع سويتش للتحميل", + "OpenRyujinxFolderTooltip": "فتح مجلد نظام ملفات ريوجينكس", "OpenRyujinxLogsTooltip": "يفتح المجلد الذي تتم كتابة السجلات إليه", - "ExitTooltip": "الخروج من Ryujinx", + "ExitTooltip": "الخروج من ريوجينكس", "OpenSettingsTooltip": "فتح نافذة الإعدادات", - "OpenProfileManagerTooltip": "فتح نافذة إدارة ملفات تعريف المستخدمين", + "OpenProfileManagerTooltip": "فتح نافذة إدارة الملفات الشخصية للمستخدمين", "StopEmulationTooltip": "إيقاف محاكاة اللعبة الحالية والعودة إلى اختيار اللعبة", - "CheckUpdatesTooltip": "التحقق من وجود تحديثات لـ Ryujinx", + "CheckUpdatesTooltip": "التحقق من وجود تحديثات لريوجينكس", "OpenAboutTooltip": "فتح حول النافذة", "GridSize": "حجم الشبكة", "GridSizeTooltip": "تغيير حجم عناصر الشبكة", "SettingsTabSystemSystemLanguageBrazilianPortuguese": "البرتغالية البرازيلية", "AboutRyujinxContributorsButtonHeader": "رؤية جميع المساهمين", - "SettingsTabSystemAudioVolume": "الحجم:", + "SettingsTabSystemAudioVolume": "مستوى الصوت:", "AudioVolumeTooltip": "تغيير مستوى الصوت", "SettingsTabSystemEnableInternetAccess": "الوصول إلى إنترنت كضيف/وضع LAN", - "EnableInternetAccessTooltip": "للسماح للتطبيق المحاكي بالاتصال بالإنترنت.\n\nالألعاب ذات وضع الشبكة المحلية يمكن الاتصال ببعضها البعض عندما يتم تمكين هذا النظام وتكون الأنظمة متصلة بنفس نقطة الوصول. هذا يشمل وحدات التحكم الحقيقية أيضًا.\n\nلا يسمح بالاتصال بخوادم Nintendo. قد يسبب الانهيار في بعض الألعاب التي تحاول الاتصال بالإنترنت.\n\nاتركه متوقفا إن لم يكن مؤكدا.", + "EnableInternetAccessTooltip": "للسماح للتطبيق الذي تمت محاكاته بالاتصال بالإنترنت.\n\nيمكن للألعاب التي تحتوي على وضع LAN الاتصال ببعضها البعض عند تمكين ذلك وتوصيل الأنظمة بنفس نقطة الوصول. وهذا يشمل الأجهزة الحقيقية أيضا.\n\nلا يسمح بالاتصال بخوادم نينتندو. قد يتسبب في حدوث عطل في بعض الألعاب التي تحاول الاتصال بالإنترنت.\n\nاتركه معطلا إذا لم تكن متأكدا.", "GameListContextMenuManageCheatToolTip": "إدارة الغش", "GameListContextMenuManageCheat": "إدارة الغش", "GameListContextMenuManageModToolTip": "إدارة التعديلات", "GameListContextMenuManageMod": "إدارة التعديلات", "ControllerSettingsStickRange": "نطاق:", - "DialogStopEmulationTitle": "Ryujinx - إيقاف المحاكاة", + "DialogStopEmulationTitle": "ريوجينكس - إيقاف المحاكاة", "DialogStopEmulationMessage": "هل أنت متأكد أنك تريد إيقاف المحاكاة؟", - "SettingsTabCpu": "CPU", + "SettingsTabCpu": "المعالج", "SettingsTabAudio": "الصوت", "SettingsTabNetwork": "الشبكة", "SettingsTabNetworkConnection": "اتصال الشبكة", "SettingsTabCpuCache": "ذاكرة المعالج المؤقت", "SettingsTabCpuMemory": "وضع المعالج", - "DialogUpdaterFlatpakNotSupportedMessage": "الرجاء تحديث Ryujinx عبر FlatHub.", - "UpdaterDisabledWarningTitle": "التحديث معطل!", + "DialogUpdaterFlatpakNotSupportedMessage": "الرجاء تحديث ريوجينكس عبر فلات هاب.", + "UpdaterDisabledWarningTitle": "المحدث معطل!", "ControllerSettingsRotate90": "تدوير 90 درجة في اتجاه عقارب الساعة", "IconSize": "حجم الأيقونة", "IconSizeTooltip": "تغيير حجم أيقونات اللعبة", "MenuBarOptionsShowConsole": "عرض وحدة التحكم", - "ShaderCachePurgeError": "Error purging shader cache at {0}: {1}", + "ShaderCachePurgeError": "حدث خطأ أثناء تنظيف ذاكرة المظللات المؤقتة في {0}: {1}", "UserErrorNoKeys": "المفاتيح غير موجودة", "UserErrorNoFirmware": "لم يتم العثور على البرنامج الثابت", "UserErrorFirmwareParsingFailed": "خطأ في تحليل البرنامج الثابت", "UserErrorApplicationNotFound": "التطبيق غير موجود", "UserErrorUnknown": "خطأ غير معروف", "UserErrorUndefined": "خطأ غير محدد", - "UserErrorNoKeysDescription": "لم يتمكن Ryujinx من العثور على ملف 'prod.keys' الخاص بك", + "UserErrorNoKeysDescription": "لم يتمكن ريوجينكس من العثور على ملف 'prod.keys' الخاص بك", "UserErrorNoFirmwareDescription": "لم يتمكن ريوجينكس من العثور على أية برامج ثابتة مثبتة", "UserErrorFirmwareParsingFailedDescription": "لم يتمكن ريوجينكس من تحليل البرامج الثابتة المتوفرة. يحدث هذا عادة بسبب المفاتيح القديمة.", "UserErrorApplicationNotFoundDescription": "تعذر على ريوجينكس العثور على تطبيق صالح في المسار المحدد.", @@ -542,72 +648,73 @@ "OpenSetupGuideMessage": "فتح دليل الإعداد", "NoUpdate": "لا يوجد تحديث", "TitleUpdateVersionLabel": "الإصدار: {0}", - "RyujinxInfo": "Ryujinx - معلومات", - "RyujinxConfirm": "Ryujinx - تأكيد", + "RyujinxInfo": "ريوجينكس - معلومات", + "RyujinxConfirm": "ريوجينكس - تأكيد", "FileDialogAllTypes": "كل الأنواع", - "Never": "مطلقاً", - "SwkbdMinCharacters": "يجب أن يبلغ طوله {0} حرفًا على الأقل", - "SwkbdMinRangeCharacters": "يجب أن يتكون من {0}-{1} حرفًا", + "Never": "مطلقا", + "SwkbdMinCharacters": "يجب أن يبلغ طوله {0} حرفا على الأقل", + "SwkbdMinRangeCharacters": "يجب أن يتكون من {0}-{1} حرفا", "SoftwareKeyboard": "لوحة المفاتيح البرمجية", "SoftwareKeyboardModeNumeric": "يجب أن يكون 0-9 أو '.' فقط", "SoftwareKeyboardModeAlphabet": "يجب أن تكون الأحرف غير CJK فقط", "SoftwareKeyboardModeASCII": "يجب أن يكون نص ASCII فقط", - "ControllerAppletControllers": "ذراع التحكم المدعومة:", + "ControllerAppletControllers": "وحدات التحكم المدعومة:", "ControllerAppletPlayers": "اللاعبين:", "ControllerAppletDescription": "الإعدادات الحالية غير صالحة. افتح الإعدادات وأعد تكوين المدخلات الخاصة بك.", - "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.", + "ControllerAppletDocked": "تم ضبط وضع تركيب بالمنصة. يجب تعطيل التحكم المحمول.", "UpdaterRenaming": "إعادة تسمية الملفات القديمة...", - "UpdaterRenameFailed": "التحديث غير قادر على إعادة تسمية الملف: {0}", + "UpdaterRenameFailed": "المحدث غير قادر على إعادة تسمية الملف: {0}", "UpdaterAddingFiles": "إضافة ملفات جديدة...", "UpdaterExtracting": "استخراج التحديث...", "UpdaterDownloading": "تحميل التحديث...", "Game": "لعبة", - "Docked": "مركب بالمنصة", + "Docked": "تركيب بالمنصة", "Handheld": "محمول", "ConnectionError": "خطأ في الاتصال", "AboutPageDeveloperListMore": "{0} والمزيد...", "ApiError": "خطأ في API.", - "LoadingHeading": "جارٍ تحميل {0}", - "CompilingPPTC": "تجميع الـ PTC", - "CompilingShaders": "تجميع الظلال", + "LoadingHeading": "جاري تحميل {0}", + "CompilingPPTC": "تجميع الـ‫(PPTC)", + "CompilingShaders": "تجميع المظللات", "AllKeyboards": "كل لوحات المفاتيح", "OpenFileDialogTitle": "حدد ملف مدعوم لفتحه", - "OpenFolderDialogTitle": "حدد مجلدًا يحتوي على لعبة غير مضغوطة", + "OpenFolderDialogTitle": "حدد مجلدا يحتوي على لعبة غير مضغوطة", "AllSupportedFormats": "كل التنسيقات المدعومة", - "RyujinxUpdater": "تحديث Ryujinx", + "RyujinxUpdater": "محدث ريوجينكس", "SettingsTabHotkeys": "مفاتيح الاختصار في لوحة المفاتيح", "SettingsTabHotkeysHotkeys": "مفاتيح الاختصار في لوحة المفاتيح", - "SettingsTabHotkeysToggleVsyncHotkey": "تبديل VSync:", + "SettingsTabHotkeysToggleVsyncHotkey": "تبديل المزامنة العمودية:", "SettingsTabHotkeysScreenshotHotkey": "لقطة الشاشة:", "SettingsTabHotkeysShowUiHotkey": "عرض واجهة المستخدم:", "SettingsTabHotkeysPauseHotkey": "إيقاف مؤقت:", - "SettingsTabHotkeysToggleMuteHotkey": "كتم الصوت:", + "SettingsTabHotkeysToggleMuteHotkey": "كتم:", "ControllerMotionTitle": "إعدادات التحكم بالحركة", "ControllerRumbleTitle": "إعدادات الهزاز", "SettingsSelectThemeFileDialogTitle": "حدد ملف السمة", - "SettingsXamlThemeFile": "Xaml Theme File", + "SettingsXamlThemeFile": "ملف سمة Xaml", "AvatarWindowTitle": "إدارة الحسابات - الصورة الرمزية", "Amiibo": "أميبو", "Unknown": "غير معروف", "Usage": "الاستخدام", "Writable": "قابل للكتابة", - "SelectDlcDialogTitle": "حدد ملفات DLC", + "SelectDlcDialogTitle": "حدد ملفات المحتوي الإضافي", "SelectUpdateDialogTitle": "حدد ملفات التحديث", "SelectModDialogTitle": "حدد مجلد التعديل", - "UserProfileWindowTitle": "مدير ملفات تعريف المستخدمين", + "UserProfileWindowTitle": "مدير الملفات الشخصية للمستخدمين", "CheatWindowTitle": "مدير الغش", "DlcWindowTitle": "إدارة المحتوى القابل للتنزيل لـ {0} ({1})", + "ModWindowTitle": "إدارة التعديلات لـ {0} ({1})", "UpdateWindowTitle": "مدير تحديث العنوان", "CheatWindowHeading": "الغش متوفر لـ {0} [{1}]", "BuildId": "معرف البناء:", "DlcWindowHeading": "المحتويات القابلة للتنزيل {0}", "ModWindowHeading": "{0} تعديل", - "UserProfilesEditProfile": "تعديل المحددة", + "UserProfilesEditProfile": "تعديل المحدد", "Cancel": "إلغاء", "Save": "حفظ", "Discard": "تجاهل", "Paused": "متوقف مؤقتا", - "UserProfilesSetProfileImage": "تعيين صورة ملف التعريف", + "UserProfilesSetProfileImage": "تعيين صورة الملف الشخصي", "UserProfileEmptyNameError": "الاسم مطلوب", "UserProfileNoImageError": "يجب تعيين صورة الملف الشخصي", "GameUpdateWindowHeading": "إدارة التحديثات لـ {0} ({1})", @@ -616,22 +723,22 @@ "UserProfilesName": "الاسم:", "UserProfilesUserId": "معرف المستخدم:", "SettingsTabGraphicsBackend": "خلفية الرسومات", - "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", + "SettingsTabGraphicsBackendTooltip": "حدد الواجهة الخلفية للرسومات التي سيتم استخدامها في المحاكي.\n\nيعد برنامج فولكان أفضل بشكل عام لجميع بطاقات الرسومات الحديثة، طالما أن برامج التشغيل الخاصة بها محدثة. يتميز فولكان أيضا بتجميع مظللات أسرع (أقل تقطيعا) على جميع بائعي وحدات معالجة الرسومات.\n\nقد يحقق أوبن جي أل نتائج أفضل على وحدات معالجة الرسومات إنفيديا القديمة، أو على وحدات معالجة الرسومات إي إم دي القديمة على لينكس، أو على وحدات معالجة الرسومات ذات ذاكرة الوصول العشوائي للفيديوالأقل، على الرغم من أن تعثرات تجميع المظللات ستكون أكبر.\n\nاضبط على فولكان إذا لم تكن متأكدا. اضبط على أوبن جي أل إذا كانت وحدة معالجة الرسومات الخاصة بك لا تدعم فولكان حتى مع أحدث برامج تشغيل الرسومات.", "SettingsEnableTextureRecompression": "تمكين إعادة ضغط التكستر", - "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", - "SettingsTabGraphicsPreferredGpu": "GPU المفضل", - "SettingsTabGraphicsPreferredGpuTooltip": "Select the graphics card that will be used with the Vulkan graphics backend.\n\nDoes not affect the GPU that OpenGL will use.\n\nSet to the GPU flagged as \"dGPU\" if unsure. If there isn't one, leave untouched.", - "SettingsAppRequiredRestartMessage": "مطلوب إعادة تشغيل Ryujinx", - "SettingsGpuBackendRestartMessage": "Graphics Backend or GPU settings have been modified. This will require a restart to be applied", + "SettingsEnableTextureRecompressionTooltip": "يضغط تكستر ASTC من أجل تقليل استخدام ذاكرة الوصول العشوائي للفيديو.\n\nتتضمن الألعاب التي تستخدم تنسيق النسيج هذا Astral Chain وBayonetta 3 وFire Emblem Engage وMetroid Prime Remastered وSuper Mario Bros. Wonder وThe Legend of Zelda: Tears of the Kingdom.\n\nمن المحتمل أن تتعطل بطاقات الرسومات التي تحتوي على 4 جيجا بايت من ذاكرة الوصول العشوائي للفيديو أو أقل في مرحلة ما أثناء تشغيل هذه الألعاب.\n\nقم بالتمكين فقط في حالة نفاد ذاكرة الوصول العشوائي للفيديو في الألعاب المذكورة أعلاه. اتركه معطلا إذا لم تكن متأكدا.", + "SettingsTabGraphicsPreferredGpu": "وحدة معالجة الرسوميات المفضلة", + "SettingsTabGraphicsPreferredGpuTooltip": "حدد بطاقة الرسومات التي سيتم استخدامها مع الواجهة الخلفية لرسومات فولكان.\n\nلا يؤثر على وحدة معالجة الرسومات التي سيستخدمها أوبن جي أل.\n\nاضبط على وحدة معالجة الرسومات التي تم وضع علامة عليها كـ \"dGPU\" إذا لم تكن متأكدًا. إذا لم يكن هناك واحد، اتركه.", + "SettingsAppRequiredRestartMessage": "مطلوب إعادة تشغيل ريوجينكس", + "SettingsGpuBackendRestartMessage": "تم تعديل إعدادات الواجهة الخلفية للرسومات أو وحدة معالجة الرسومات. سيتطلب هذا إعادة التشغيل ليتم تطبيقه", "SettingsGpuBackendRestartSubMessage": "\n\nهل تريد إعادة التشغيل الآن؟", - "RyujinxUpdaterMessage": "هل تريد تحديث Ryujinx إلى أحدث إصدار؟", + "RyujinxUpdaterMessage": "هل تريد تحديث ريوجينكس إلى أحدث إصدار؟", "SettingsTabHotkeysVolumeUpHotkey": "زيادة مستوى الصوت:", "SettingsTabHotkeysVolumeDownHotkey": "خفض مستوى الصوت:", - "SettingsEnableMacroHLE": "Enable Macro HLE", - "SettingsEnableMacroHLETooltip": "High-level emulation of GPU Macro code.\n\nImproves performance, but may cause graphical glitches in some games.\n\nLeave ON if unsure.", - "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", - "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", - "VolumeShort": "الحجم", + "SettingsEnableMacroHLE": "تمكين Maro HLE", + "SettingsEnableMacroHLETooltip": "محاكاة عالية المستوى لكود مايكرو وحدة معالجة الرسوميات.\n\nيعمل على تحسين الأداء، ولكنه قد يسبب خللا رسوميا في بعض الألعاب.\n\nاتركه مفعلا إذا لم تكن متأكدا.", + "SettingsEnableColorSpacePassthrough": "عبور مساحة اللون", + "SettingsEnableColorSpacePassthroughTooltip": "يوجه واجهة فولكان الخلفية لتمرير معلومات الألوان دون تحديد مساحة اللون. بالنسبة للمستخدمين الذين لديهم شاشات ذات نطاق واسع، قد يؤدي ذلك إلى الحصول على ألوان أكثر حيوية، على حساب صحة الألوان.", + "VolumeShort": "مستوى", "UserProfilesManageSaves": "إدارة الحفظ", "DeleteUserSave": "هل تريد حذف حفظ المستخدم لهذه اللعبة؟", "IrreversibleActionNote": "هذا الإجراء لا يمكن التراجع عنه.", @@ -642,12 +749,12 @@ "Search": "بحث", "UserProfilesRecoverLostAccounts": "استعادة الحسابات المفقودة", "Recover": "استعادة", - "UserProfilesRecoverHeading": "تم العثور على الحفظ للحسابات التالية", - "UserProfilesRecoverEmptyList": "لا توجد ملفات تعريف لاستردادها", - "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", + "UserProfilesRecoverHeading": "تم العثور على حفظ للحسابات التالية", + "UserProfilesRecoverEmptyList": "لا توجد ملفات شخصية لاستردادها", + "GraphicsAATooltip": "يتم تطبيق تنعيم الحواف على عرض اللعبة.\n\nسوف يقوم FXAA بتعتيم معظم الصورة، بينما سيحاول SMAA العثور على حواف خشنة وتنعيمها.\n\nلا ينصح باستخدامه مع فلتر FSR لتكبير.\n\nيمكن تغيير هذا الخيار أثناء تشغيل اللعبة بالنقر فوق \"تطبيق\" أدناه؛ يمكنك ببساطة تحريك نافذة الإعدادات جانبا والتجربة حتى تجد المظهر المفضل للعبة.\n\nاتركه على لا شيء إذا لم تكن متأكدا.", "GraphicsAALabel": "تنعيم الحواف:", "GraphicsScalingFilterLabel": "فلتر التكبير:", - "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterTooltip": "اختر فلتر التكبير الذي سيتم تطبيقه عند استخدام مقياس الدقة.\n\nيعمل Bilinear بشكل جيد مع الألعاب ثلاثية الأبعاد وهو خيار افتراضي آمن.\n\nيوصى باستخدام Nearest لألعاب البكسل الفنية.\n\nFSR 1.0 هو مجرد مرشح توضيحي، ولا ينصح باستخدامه مع FXAA أو SMAA.\n\nيمكن تغيير هذا الخيار أثناء تشغيل اللعبة بالنقر فوق \"تطبيق\" أدناه؛ يمكنك ببساطة تحريك نافذة الإعدادات جانبا والتجربة حتى تجد المظهر المفضل للعبة.\n\nاتركه على Bilinear إذا لم تكن متأكدا.", "GraphicsScalingFilterBilinear": "Bilinear", "GraphicsScalingFilterNearest": "Nearest", "GraphicsScalingFilterFsr": "FSR", @@ -660,14 +767,14 @@ "UserEditorTitle": "تعديل المستخدم", "UserEditorTitleCreate": "إنشاء مستخدم", "SettingsTabNetworkInterface": "واجهة الشبكة:", - "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", + "NetworkInterfaceTooltip": "واجهة الشبكة مستخدمة لميزات LAN/LDN.\n\nبالاشتراك مع VPN أو XLink Kai ولعبة تدعم LAN، يمكن استخدامها لتزييف اتصال الشبكة نفسها عبر الإنترنت.\n\nاتركه على الافتراضي إذا لم تكن متأكدا.", "NetworkInterfaceDefault": "افتراضي", - "PackagingShaders": "Packaging Shaders", - "AboutChangelogButton": "عرض سجل التغييرات على GitHub", + "PackagingShaders": "تعبئة المظللات", + "AboutChangelogButton": "عرض سجل التغييرات على غيت هاب", "AboutChangelogButtonTooltipMessage": "انقر لفتح سجل التغيير لهذا الإصدار في متصفحك الافتراضي.", "SettingsTabNetworkMultiplayer": "لعب جماعي", - "MultiplayerMode": "النمط:", - "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.", + "MultiplayerMode": "الوضع:", + "MultiplayerModeTooltip": "تغيير وضع LDN متعدد اللاعبين.\n\nسوف يقوم LdnMitm بتعديل وظيفة اللعب المحلية/اللاسلكية المحلية في الألعاب لتعمل كما لو كانت شبكة LAN، مما يسمح باتصالات الشبكة المحلية نفسها مع محاكيات ريوجينكس الأخرى وأجهزة نينتندو سويتش المخترقة التي تم تثبيت وحدة ldn_mitm عليها.\n\nيتطلب وضع اللاعبين المتعددين أن يكون جميع اللاعبين على نفس إصدار اللعبة (على سبيل المثال، يتعذر على الإصدار 13.0.1 من سوبر سماش برذرز ألتميت الاتصال بالإصدار 13.0.0).\n\nاتركه معطلا إذا لم تكن متأكدا.", "MultiplayerModeDisabled": "معطل", "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/de_DE.json b/src/Ryujinx/Assets/Locales/de_DE.json index 71af4f3feb..4012931985 100644 --- a/src/Ryujinx/Assets/Locales/de_DE.json +++ b/src/Ryujinx/Assets/Locales/de_DE.json @@ -30,6 +30,10 @@ "MenuBarToolsManageFileTypes": "Dateitypen verwalten", "MenuBarToolsInstallFileTypes": "Dateitypen installieren", "MenuBarToolsUninstallFileTypes": "Dateitypen deinstallieren", + "MenuBarView": "_Ansicht", + "MenuBarViewWindow": "Fenstergröße", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", "MenuBarHelp": "_Hilfe", "MenuBarHelpCheckForUpdates": "Nach Updates suchen", "MenuBarHelpAbout": "Über Ryujinx", @@ -92,6 +96,7 @@ "SettingsTabGeneralEnableDiscordRichPresence": "Aktiviere die Statusanzeige für Discord", "SettingsTabGeneralCheckUpdatesOnLaunch": "Beim Start nach Updates suchen", "SettingsTabGeneralShowConfirmExitDialog": "Zeige den \"Beenden bestätigen\"-Dialog", + "SettingsTabGeneralRememberWindowState": "Fenstergröße/-position merken", "SettingsTabGeneralHideCursor": "Mauszeiger ausblenden", "SettingsTabGeneralHideCursorNever": "Niemals", "SettingsTabGeneralHideCursorOnIdle": "Mauszeiger bei Inaktivität ausblenden", @@ -266,6 +271,107 @@ "ControllerSettingsMotionGyroDeadzone": "Gyro-Deadzone:", "ControllerSettingsSave": "Speichern", "ControllerSettingsClose": "Schließen", + "KeyUnknown": "Unbekannt", + "KeyShiftLeft": "Shift Left", + "KeyShiftRight": "Shift Right", + "KeyControlLeft": "Ctrl Left", + "KeyMacControlLeft": "⌃ Left", + "KeyControlRight": "Ctrl Right", + "KeyMacControlRight": "⌃ Right", + "KeyAltLeft": "Alt Left", + "KeyMacAltLeft": "⌥ Left", + "KeyAltRight": "Alt Right", + "KeyMacAltRight": "⌥ Right", + "KeyWinLeft": "⊞ Left", + "KeyMacWinLeft": "⌘ Left", + "KeyWinRight": "⊞ Right", + "KeyMacWinRight": "⌘ Right", + "KeyMenu": "Menu", + "KeyUp": "Up", + "KeyDown": "Down", + "KeyLeft": "Left", + "KeyRight": "Right", + "KeyEnter": "Enter", + "KeyEscape": "Escape", + "KeySpace": "Space", + "KeyTab": "Tab", + "KeyBackSpace": "Backspace", + "KeyInsert": "Insert", + "KeyDelete": "Delete", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "Print Screen", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "Clear", + "KeyKeypad0": "Keypad 0", + "KeyKeypad1": "Keypad 1", + "KeyKeypad2": "Keypad 2", + "KeyKeypad3": "Keypad 3", + "KeyKeypad4": "Keypad 4", + "KeyKeypad5": "Keypad 5", + "KeyKeypad6": "Keypad 6", + "KeyKeypad7": "Keypad 7", + "KeyKeypad8": "Keypad 8", + "KeyKeypad9": "Keypad 9", + "KeyKeypadDivide": "Keypad Divide", + "KeyKeypadMultiply": "Keypad Multiply", + "KeyKeypadSubtract": "Keypad Subtract", + "KeyKeypadAdd": "Keypad Add", + "KeyKeypadDecimal": "Keypad Decimal", + "KeyKeypadEnter": "Keypad Enter", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "Unbound", + "GamepadLeftStick": "L Stick Button", + "GamepadRightStick": "R Stick Button", + "GamepadLeftShoulder": "Left Shoulder", + "GamepadRightShoulder": "Right Shoulder", + "GamepadLeftTrigger": "Left Trigger", + "GamepadRightTrigger": "Right Trigger", + "GamepadDpadUp": "Up", + "GamepadDpadDown": "Down", + "GamepadDpadLeft": "Left", + "GamepadDpadRight": "Right", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "Guide", + "GamepadMisc1": "Misc", + "GamepadPaddle1": "Paddle 1", + "GamepadPaddle2": "Paddle 2", + "GamepadPaddle3": "Paddle 3", + "GamepadPaddle4": "Paddle 4", + "GamepadTouchpad": "Touchpad", + "GamepadSingleLeftTrigger0": "Left Trigger 0", + "GamepadSingleRightTrigger0": "Right Trigger 0", + "GamepadSingleLeftTrigger1": "Left Trigger 1", + "GamepadSingleRightTrigger1": "Right Trigger 1", + "StickLeft": "Left Stick", + "StickRight": "Right Stick", "UserProfilesSelectedUserProfile": "Ausgewähltes Profil:", "UserProfilesSaveProfileName": "Profilname speichern", "UserProfilesChangeProfileImage": "Profilbild ändern", @@ -597,6 +703,7 @@ "UserProfileWindowTitle": "Benutzerprofile verwalten", "CheatWindowTitle": "Spiel-Cheats verwalten", "DlcWindowTitle": "Spiel-DLC verwalten", + "ModWindowTitle": "Manage Mods for {0} ({1})", "UpdateWindowTitle": "Spiel-Updates verwalten", "CheatWindowHeading": "Cheats verfügbar für {0} [{1}]", "BuildId": "BuildId:", diff --git a/src/Ryujinx/Assets/Locales/el_GR.json b/src/Ryujinx/Assets/Locales/el_GR.json index ba5c7078c6..ccdf6e0e41 100644 --- a/src/Ryujinx/Assets/Locales/el_GR.json +++ b/src/Ryujinx/Assets/Locales/el_GR.json @@ -30,6 +30,10 @@ "MenuBarToolsManageFileTypes": "Διαχείριση τύπων αρχείων", "MenuBarToolsInstallFileTypes": "Εγκαταστήσετε τύπους αρχείων.", "MenuBarToolsUninstallFileTypes": "Απεγκαταστήσετε τύπους αρχείων", + "MenuBarView": "_View", + "MenuBarViewWindow": "Window Size", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", "MenuBarHelp": "_Βοήθεια", "MenuBarHelpCheckForUpdates": "Έλεγχος για Ενημερώσεις", "MenuBarHelpAbout": "Σχετικά με", @@ -92,6 +96,7 @@ "SettingsTabGeneralEnableDiscordRichPresence": "Ενεργοποίηση Εμπλουτισμένης Παρουσίας Discord", "SettingsTabGeneralCheckUpdatesOnLaunch": "Έλεγχος για Ενημερώσεις στην Εκκίνηση", "SettingsTabGeneralShowConfirmExitDialog": "Εμφάνιση διαλόγου \"Επιβεβαίωση Εξόδου\".", + "SettingsTabGeneralRememberWindowState": "Remember Window Size/Position", "SettingsTabGeneralHideCursor": "Απόκρυψη Κέρσορα:", "SettingsTabGeneralHideCursorNever": "Ποτέ", "SettingsTabGeneralHideCursorOnIdle": "Απόκρυψη Δρομέα στην Αδράνεια", @@ -266,6 +271,107 @@ "ControllerSettingsMotionGyroDeadzone": "Νεκρή Ζώνη Γυροσκοπίου:", "ControllerSettingsSave": "Αποθήκευση", "ControllerSettingsClose": "Κλείσιμο", + "KeyUnknown": "Unknown", + "KeyShiftLeft": "Shift Left", + "KeyShiftRight": "Shift Right", + "KeyControlLeft": "Ctrl Left", + "KeyMacControlLeft": "⌃ Left", + "KeyControlRight": "Ctrl Right", + "KeyMacControlRight": "⌃ Right", + "KeyAltLeft": "Alt Left", + "KeyMacAltLeft": "⌥ Left", + "KeyAltRight": "Alt Right", + "KeyMacAltRight": "⌥ Right", + "KeyWinLeft": "⊞ Left", + "KeyMacWinLeft": "⌘ Left", + "KeyWinRight": "⊞ Right", + "KeyMacWinRight": "⌘ Right", + "KeyMenu": "Menu", + "KeyUp": "Up", + "KeyDown": "Down", + "KeyLeft": "Left", + "KeyRight": "Right", + "KeyEnter": "Enter", + "KeyEscape": "Escape", + "KeySpace": "Space", + "KeyTab": "Tab", + "KeyBackSpace": "Backspace", + "KeyInsert": "Insert", + "KeyDelete": "Delete", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "Print Screen", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "Clear", + "KeyKeypad0": "Keypad 0", + "KeyKeypad1": "Keypad 1", + "KeyKeypad2": "Keypad 2", + "KeyKeypad3": "Keypad 3", + "KeyKeypad4": "Keypad 4", + "KeyKeypad5": "Keypad 5", + "KeyKeypad6": "Keypad 6", + "KeyKeypad7": "Keypad 7", + "KeyKeypad8": "Keypad 8", + "KeyKeypad9": "Keypad 9", + "KeyKeypadDivide": "Keypad Divide", + "KeyKeypadMultiply": "Keypad Multiply", + "KeyKeypadSubtract": "Keypad Subtract", + "KeyKeypadAdd": "Keypad Add", + "KeyKeypadDecimal": "Keypad Decimal", + "KeyKeypadEnter": "Keypad Enter", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "Unbound", + "GamepadLeftStick": "L Stick Button", + "GamepadRightStick": "R Stick Button", + "GamepadLeftShoulder": "Left Shoulder", + "GamepadRightShoulder": "Right Shoulder", + "GamepadLeftTrigger": "Left Trigger", + "GamepadRightTrigger": "Right Trigger", + "GamepadDpadUp": "Up", + "GamepadDpadDown": "Down", + "GamepadDpadLeft": "Left", + "GamepadDpadRight": "Right", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "Guide", + "GamepadMisc1": "Misc", + "GamepadPaddle1": "Paddle 1", + "GamepadPaddle2": "Paddle 2", + "GamepadPaddle3": "Paddle 3", + "GamepadPaddle4": "Paddle 4", + "GamepadTouchpad": "Touchpad", + "GamepadSingleLeftTrigger0": "Left Trigger 0", + "GamepadSingleRightTrigger0": "Right Trigger 0", + "GamepadSingleLeftTrigger1": "Left Trigger 1", + "GamepadSingleRightTrigger1": "Right Trigger 1", + "StickLeft": "Left Stick", + "StickRight": "Right Stick", "UserProfilesSelectedUserProfile": "Επιλεγμένο Προφίλ Χρήστη:", "UserProfilesSaveProfileName": "Αποθήκευση Ονόματος Προφίλ", "UserProfilesChangeProfileImage": "Αλλαγή Εικόνας Προφίλ", @@ -597,6 +703,7 @@ "UserProfileWindowTitle": "Διαχειριστής Προφίλ Χρήστη", "CheatWindowTitle": "Διαχειριστής των Cheats", "DlcWindowTitle": "Downloadable Content Manager", + "ModWindowTitle": "Manage Mods for {0} ({1})", "UpdateWindowTitle": "Διαχειριστής Ενημερώσεων Τίτλου", "CheatWindowHeading": "Διαθέσιμα Cheats για {0} [{1}]", "BuildId": "BuildId:", diff --git a/src/Ryujinx/Assets/Locales/es_ES.json b/src/Ryujinx/Assets/Locales/es_ES.json index 38499c046f..e58fa5dcf8 100644 --- a/src/Ryujinx/Assets/Locales/es_ES.json +++ b/src/Ryujinx/Assets/Locales/es_ES.json @@ -30,6 +30,10 @@ "MenuBarToolsManageFileTypes": "Administrar tipos de archivo", "MenuBarToolsInstallFileTypes": "Instalar tipos de archivo", "MenuBarToolsUninstallFileTypes": "Desinstalar tipos de archivo", + "MenuBarView": "_View", + "MenuBarViewWindow": "Window Size", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", "MenuBarHelp": "_Ayuda", "MenuBarHelpCheckForUpdates": "Buscar actualizaciones", "MenuBarHelpAbout": "Acerca de", @@ -92,6 +96,7 @@ "SettingsTabGeneralEnableDiscordRichPresence": "Habilitar estado en Discord", "SettingsTabGeneralCheckUpdatesOnLaunch": "Buscar actualizaciones al iniciar", "SettingsTabGeneralShowConfirmExitDialog": "Mostrar diálogo de confirmación al cerrar", + "SettingsTabGeneralRememberWindowState": "Remember Window Size/Position", "SettingsTabGeneralHideCursor": "Esconder el cursor:", "SettingsTabGeneralHideCursorNever": "Nunca", "SettingsTabGeneralHideCursorOnIdle": "Ocultar cursor cuando esté inactivo", @@ -155,7 +160,7 @@ "SettingsTabGraphicsResolutionScaleNative": "Nativa (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "x2 (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "x3 (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Not recommended)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (no recomendado)", "SettingsTabGraphicsAspectRatio": "Relación de aspecto:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -266,6 +271,107 @@ "ControllerSettingsMotionGyroDeadzone": "Zona muerta de Gyro:", "ControllerSettingsSave": "Guardar", "ControllerSettingsClose": "Cerrar", + "KeyUnknown": "Desconocido", + "KeyShiftLeft": "Shift Left", + "KeyShiftRight": "Shift Right", + "KeyControlLeft": "Ctrl Left", + "KeyMacControlLeft": "⌃ Left", + "KeyControlRight": "Ctrl Right", + "KeyMacControlRight": "⌃ Right", + "KeyAltLeft": "Alt Left", + "KeyMacAltLeft": "⌥ Left", + "KeyAltRight": "Alt Right", + "KeyMacAltRight": "⌥ Right", + "KeyWinLeft": "⊞ Left", + "KeyMacWinLeft": "⌘ Left", + "KeyWinRight": "⊞ Right", + "KeyMacWinRight": "⌘ Right", + "KeyMenu": "Menu", + "KeyUp": "Up", + "KeyDown": "Down", + "KeyLeft": "Left", + "KeyRight": "Right", + "KeyEnter": "Enter", + "KeyEscape": "Escape", + "KeySpace": "Space", + "KeyTab": "Tab", + "KeyBackSpace": "Backspace", + "KeyInsert": "Insert", + "KeyDelete": "Delete", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "Print Screen", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "Clear", + "KeyKeypad0": "Keypad 0", + "KeyKeypad1": "Keypad 1", + "KeyKeypad2": "Keypad 2", + "KeyKeypad3": "Keypad 3", + "KeyKeypad4": "Keypad 4", + "KeyKeypad5": "Keypad 5", + "KeyKeypad6": "Keypad 6", + "KeyKeypad7": "Keypad 7", + "KeyKeypad8": "Keypad 8", + "KeyKeypad9": "Keypad 9", + "KeyKeypadDivide": "Keypad Divide", + "KeyKeypadMultiply": "Keypad Multiply", + "KeyKeypadSubtract": "Keypad Subtract", + "KeyKeypadAdd": "Keypad Add", + "KeyKeypadDecimal": "Keypad Decimal", + "KeyKeypadEnter": "Keypad Enter", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "Unbound", + "GamepadLeftStick": "L Stick Button", + "GamepadRightStick": "R Stick Button", + "GamepadLeftShoulder": "Left Shoulder", + "GamepadRightShoulder": "Right Shoulder", + "GamepadLeftTrigger": "Left Trigger", + "GamepadRightTrigger": "Right Trigger", + "GamepadDpadUp": "Up", + "GamepadDpadDown": "Down", + "GamepadDpadLeft": "Left", + "GamepadDpadRight": "Right", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "Guide", + "GamepadMisc1": "Misc", + "GamepadPaddle1": "Paddle 1", + "GamepadPaddle2": "Paddle 2", + "GamepadPaddle3": "Paddle 3", + "GamepadPaddle4": "Paddle 4", + "GamepadTouchpad": "Touchpad", + "GamepadSingleLeftTrigger0": "Left Trigger 0", + "GamepadSingleRightTrigger0": "Right Trigger 0", + "GamepadSingleLeftTrigger1": "Left Trigger 1", + "GamepadSingleRightTrigger1": "Right Trigger 1", + "StickLeft": "Left Stick", + "StickRight": "Right Stick", "UserProfilesSelectedUserProfile": "Perfil de usuario seleccionado:", "UserProfilesSaveProfileName": "Guardar nombre de perfil", "UserProfilesChangeProfileImage": "Cambiar imagen de perfil", @@ -597,6 +703,7 @@ "UserProfileWindowTitle": "Administrar perfiles de usuario", "CheatWindowTitle": "Administrar cheats", "DlcWindowTitle": "Administrar contenido descargable", + "ModWindowTitle": "Manage Mods for {0} ({1})", "UpdateWindowTitle": "Administrar actualizaciones", "CheatWindowHeading": "Cheats disponibles para {0} [{1}]", "BuildId": "Id de compilación:", @@ -647,12 +754,12 @@ "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", "GraphicsAALabel": "Suavizado de bordes:", "GraphicsScalingFilterLabel": "Filtro de escalado:", - "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", - "GraphicsScalingFilterBilinear": "Bilinear", - "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterTooltip": "Elija el filtro de escala que se aplicará al utilizar la escala de resolución.\n\nBilinear funciona bien para juegos 3D y es una opción predeterminada segura.\n\nSe recomienda el bilinear para juegos de pixel art.\n\nFSR 1.0 es simplemente un filtro de afilado, no se recomienda su uso con FXAA o SMAA.\n\nEsta opción se puede cambiar mientras se ejecuta un juego haciendo clic en \"Aplicar\" a continuación; simplemente puedes mover la ventana de configuración a un lado y experimentar hasta que encuentres tu estilo preferido para un juego.\n\nDéjelo en BILINEAR si no está seguro.", + "GraphicsScalingFilterBilinear": "Bilinear\n", + "GraphicsScalingFilterNearest": "Cercano", "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "Nivel", - "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", + "GraphicsScalingFilterLevelTooltip": "Ajuste el nivel de nitidez FSR 1.0. Mayor es más nítido.", "SmaaLow": "SMAA Bajo", "SmaaMedium": "SMAA Medio", "SmaaHigh": "SMAA Alto", @@ -660,14 +767,14 @@ "UserEditorTitle": "Editar usuario", "UserEditorTitleCreate": "Crear Usuario", "SettingsTabNetworkInterface": "Interfaz de Red", - "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", + "NetworkInterfaceTooltip": "Interfaz de red usada para características LAN/LDN.\n\njunto con una VPN o XLink Kai y un juego con soporte LAN, puede usarse para suplantar una conexión de la misma red a través de Internet.\n\nDeje en DEFAULT si no está seguro.", "NetworkInterfaceDefault": "Predeterminado", "PackagingShaders": "Empaquetando sombreadores", "AboutChangelogButton": "Ver registro de cambios en GitHub", "AboutChangelogButtonTooltipMessage": "Haga clic para abrir el registro de cambios para esta versión en su navegador predeterminado.", "SettingsTabNetworkMultiplayer": "Multijugador", "MultiplayerMode": "Modo:", - "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.", - "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeTooltip": "Cambiar modo LDN multijugador.\n\nLdnMitm modificará la funcionalidad local de juego inalámbrico para funcionar como si fuera LAN, permitiendo locales conexiones de la misma red con otras instancias de Ryujinx y consolas hackeadas de Nintendo Switch que tienen instalado el módulo ldn_mitm.\n\nMultijugador requiere que todos los jugadores estén en la misma versión del juego (por ejemplo, Super Smash Bros. Ultimate v13.0.1 no se puede conectar a v13.0.0).\n\nDejar DESACTIVADO si no está seguro.", + "MultiplayerModeDisabled": "Deshabilitar", "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/fr_FR.json b/src/Ryujinx/Assets/Locales/fr_FR.json index bb2864cf2c..99a060650c 100644 --- a/src/Ryujinx/Assets/Locales/fr_FR.json +++ b/src/Ryujinx/Assets/Locales/fr_FR.json @@ -30,6 +30,10 @@ "MenuBarToolsManageFileTypes": "Gérer les types de fichiers", "MenuBarToolsInstallFileTypes": "Installer les types de fichiers", "MenuBarToolsUninstallFileTypes": "Désinstaller les types de fichiers", + "MenuBarView": "_View", + "MenuBarViewWindow": "Window Size", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", "MenuBarHelp": "_Aide", "MenuBarHelpCheckForUpdates": "Vérifier les mises à jour", "MenuBarHelpAbout": "À propos", @@ -92,6 +96,7 @@ "SettingsTabGeneralEnableDiscordRichPresence": "Activer Discord Rich Presence", "SettingsTabGeneralCheckUpdatesOnLaunch": "Vérifier les mises à jour au démarrage", "SettingsTabGeneralShowConfirmExitDialog": "Afficher le message de \"Confirmation de sortie\"", + "SettingsTabGeneralRememberWindowState": "Remember Window Size/Position", "SettingsTabGeneralHideCursor": "Masquer le Curseur :", "SettingsTabGeneralHideCursorNever": "Jamais", "SettingsTabGeneralHideCursorOnIdle": "Masquer le curseur si inactif", @@ -266,6 +271,107 @@ "ControllerSettingsMotionGyroDeadzone": "Zone morte du gyroscope:", "ControllerSettingsSave": "Enregistrer", "ControllerSettingsClose": "Fermer", + "KeyUnknown": "Unknown", + "KeyShiftLeft": "Shift Left", + "KeyShiftRight": "Shift Right", + "KeyControlLeft": "Ctrl Left", + "KeyMacControlLeft": "⌃ Left", + "KeyControlRight": "Ctrl Right", + "KeyMacControlRight": "⌃ Right", + "KeyAltLeft": "Alt Left", + "KeyMacAltLeft": "⌥ Left", + "KeyAltRight": "Alt Right", + "KeyMacAltRight": "⌥ Right", + "KeyWinLeft": "⊞ Left", + "KeyMacWinLeft": "⌘ Left", + "KeyWinRight": "⊞ Right", + "KeyMacWinRight": "⌘ Right", + "KeyMenu": "Menu", + "KeyUp": "Up", + "KeyDown": "Down", + "KeyLeft": "Left", + "KeyRight": "Right", + "KeyEnter": "Enter", + "KeyEscape": "Escape", + "KeySpace": "Space", + "KeyTab": "Tab", + "KeyBackSpace": "Backspace", + "KeyInsert": "Insert", + "KeyDelete": "Delete", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "Print Screen", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "Clear", + "KeyKeypad0": "Keypad 0", + "KeyKeypad1": "Keypad 1", + "KeyKeypad2": "Keypad 2", + "KeyKeypad3": "Keypad 3", + "KeyKeypad4": "Keypad 4", + "KeyKeypad5": "Keypad 5", + "KeyKeypad6": "Keypad 6", + "KeyKeypad7": "Keypad 7", + "KeyKeypad8": "Keypad 8", + "KeyKeypad9": "Keypad 9", + "KeyKeypadDivide": "Keypad Divide", + "KeyKeypadMultiply": "Keypad Multiply", + "KeyKeypadSubtract": "Keypad Subtract", + "KeyKeypadAdd": "Keypad Add", + "KeyKeypadDecimal": "Keypad Decimal", + "KeyKeypadEnter": "Keypad Enter", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "Unbound", + "GamepadLeftStick": "L Stick Button", + "GamepadRightStick": "R Stick Button", + "GamepadLeftShoulder": "Left Shoulder", + "GamepadRightShoulder": "Right Shoulder", + "GamepadLeftTrigger": "Left Trigger", + "GamepadRightTrigger": "Right Trigger", + "GamepadDpadUp": "Up", + "GamepadDpadDown": "Down", + "GamepadDpadLeft": "Left", + "GamepadDpadRight": "Right", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "Guide", + "GamepadMisc1": "Misc", + "GamepadPaddle1": "Paddle 1", + "GamepadPaddle2": "Paddle 2", + "GamepadPaddle3": "Paddle 3", + "GamepadPaddle4": "Paddle 4", + "GamepadTouchpad": "Touchpad", + "GamepadSingleLeftTrigger0": "Left Trigger 0", + "GamepadSingleRightTrigger0": "Right Trigger 0", + "GamepadSingleLeftTrigger1": "Left Trigger 1", + "GamepadSingleRightTrigger1": "Right Trigger 1", + "StickLeft": "Left Stick", + "StickRight": "Right Stick", "UserProfilesSelectedUserProfile": "Profil utilisateur sélectionné :", "UserProfilesSaveProfileName": "Enregistrer le nom du profil", "UserProfilesChangeProfileImage": "Changer l'image du profil", @@ -597,6 +703,7 @@ "UserProfileWindowTitle": "Gestionnaire de profils utilisateur", "CheatWindowTitle": "Gestionnaire de triches", "DlcWindowTitle": "Gérer le contenu téléchargeable pour {0} ({1})", + "ModWindowTitle": "Gérer les mods pour {0} ({1})", "UpdateWindowTitle": "Gestionnaire de mises à jour", "CheatWindowHeading": "Cheats disponibles pour {0} [{1}]", "BuildId": "BuildId:", @@ -648,8 +755,8 @@ "GraphicsAALabel": "Anticrénelage :", "GraphicsScalingFilterLabel": "Filtre de mise à l'échelle :", "GraphicsScalingFilterTooltip": "Choisissez le filtre de mise à l'échelle qui sera appliqué lors de l'utilisation de la mise à l'échelle de la résolution.\n\nLe filtre bilinéaire fonctionne bien pour les jeux en 3D et constitue une option par défaut sûre.\n\nLe filtre le plus proche est recommandé pour les jeux de pixel art.\n\nFSR 1.0 est simplement un filtre de netteté, non recommandé pour une utilisation avec FXAA ou SMAA.\n\nCette option peut être modifiée pendant qu'un jeu est en cours d'exécution en cliquant sur \"Appliquer\" ci-dessous ; vous pouvez simplement déplacer la fenêtre des paramètres de côté et expérimenter jusqu'à ce que vous trouviez l'aspect souhaité pour un jeu.\n\nLaissez sur BILINEAR si vous n'êtes pas sûr.", - "GraphicsScalingFilterBilinear": "Bilinear", - "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterBilinear": "Bilinéaire", + "GraphicsScalingFilterNearest": "Le plus proche", "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "Niveau ", "GraphicsScalingFilterLevelTooltip": "Définissez le niveau de netteté FSR 1.0. Plus élevé signifie plus net.", @@ -668,6 +775,6 @@ "SettingsTabNetworkMultiplayer": "Multijoueur", "MultiplayerMode": "Mode :", "MultiplayerModeTooltip": "Changer le mode multijoueur LDN.\n\nLdnMitm modifiera la fonctionnalité de jeu sans fil local/jeu local dans les jeux pour fonctionner comme s'il s'agissait d'un LAN, permettant des connexions locales sur le même réseau avec d'autres instances de Ryujinx et des consoles Nintendo Switch piratées ayant le module ldn_mitm installé.\n\nLe multijoueur nécessite que tous les joueurs soient sur la même version du jeu (par exemple, Super Smash Bros. Ultimate v13.0.1 ne peut pas se connecter à v13.0.0).\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr.", - "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeDisabled": "Désactivé", "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/he_IL.json b/src/Ryujinx/Assets/Locales/he_IL.json index 16d68b775c..848f780809 100644 --- a/src/Ryujinx/Assets/Locales/he_IL.json +++ b/src/Ryujinx/Assets/Locales/he_IL.json @@ -30,6 +30,10 @@ "MenuBarToolsManageFileTypes": "ניהול סוגי קבצים", "MenuBarToolsInstallFileTypes": "סוגי קבצי התקנה", "MenuBarToolsUninstallFileTypes": "סוגי קבצי הסרה", + "MenuBarView": "_View", + "MenuBarViewWindow": "Window Size", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", "MenuBarHelp": "_עזרה", "MenuBarHelpCheckForUpdates": "חפש עדכונים", "MenuBarHelpAbout": "אודות", @@ -92,6 +96,7 @@ "SettingsTabGeneralEnableDiscordRichPresence": "הפעלת תצוגה עשירה בדיסקורד", "SettingsTabGeneralCheckUpdatesOnLaunch": "בדוק אם קיימים עדכונים בהפעלה", "SettingsTabGeneralShowConfirmExitDialog": "הראה דיאלוג \"אשר יציאה\"", + "SettingsTabGeneralRememberWindowState": "Remember Window Size/Position", "SettingsTabGeneralHideCursor": "הסתר את הסמן", "SettingsTabGeneralHideCursorNever": "אף פעם", "SettingsTabGeneralHideCursorOnIdle": "במצב סרק", @@ -266,6 +271,107 @@ "ControllerSettingsMotionGyroDeadzone": "שטח מת של הג'ירוסקופ:", "ControllerSettingsSave": "שמירה", "ControllerSettingsClose": "סגירה", + "KeyUnknown": "Unknown", + "KeyShiftLeft": "Shift Left", + "KeyShiftRight": "Shift Right", + "KeyControlLeft": "Ctrl Left", + "KeyMacControlLeft": "⌃ Left", + "KeyControlRight": "Ctrl Right", + "KeyMacControlRight": "⌃ Right", + "KeyAltLeft": "Alt Left", + "KeyMacAltLeft": "⌥ Left", + "KeyAltRight": "Alt Right", + "KeyMacAltRight": "⌥ Right", + "KeyWinLeft": "⊞ Left", + "KeyMacWinLeft": "⌘ Left", + "KeyWinRight": "⊞ Right", + "KeyMacWinRight": "⌘ Right", + "KeyMenu": "Menu", + "KeyUp": "Up", + "KeyDown": "Down", + "KeyLeft": "Left", + "KeyRight": "Right", + "KeyEnter": "Enter", + "KeyEscape": "Escape", + "KeySpace": "Space", + "KeyTab": "Tab", + "KeyBackSpace": "Backspace", + "KeyInsert": "Insert", + "KeyDelete": "Delete", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "Print Screen", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "Clear", + "KeyKeypad0": "Keypad 0", + "KeyKeypad1": "Keypad 1", + "KeyKeypad2": "Keypad 2", + "KeyKeypad3": "Keypad 3", + "KeyKeypad4": "Keypad 4", + "KeyKeypad5": "Keypad 5", + "KeyKeypad6": "Keypad 6", + "KeyKeypad7": "Keypad 7", + "KeyKeypad8": "Keypad 8", + "KeyKeypad9": "Keypad 9", + "KeyKeypadDivide": "Keypad Divide", + "KeyKeypadMultiply": "Keypad Multiply", + "KeyKeypadSubtract": "Keypad Subtract", + "KeyKeypadAdd": "Keypad Add", + "KeyKeypadDecimal": "Keypad Decimal", + "KeyKeypadEnter": "Keypad Enter", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "Unbound", + "GamepadLeftStick": "L Stick Button", + "GamepadRightStick": "R Stick Button", + "GamepadLeftShoulder": "Left Shoulder", + "GamepadRightShoulder": "Right Shoulder", + "GamepadLeftTrigger": "Left Trigger", + "GamepadRightTrigger": "Right Trigger", + "GamepadDpadUp": "Up", + "GamepadDpadDown": "Down", + "GamepadDpadLeft": "Left", + "GamepadDpadRight": "Right", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "Guide", + "GamepadMisc1": "Misc", + "GamepadPaddle1": "Paddle 1", + "GamepadPaddle2": "Paddle 2", + "GamepadPaddle3": "Paddle 3", + "GamepadPaddle4": "Paddle 4", + "GamepadTouchpad": "Touchpad", + "GamepadSingleLeftTrigger0": "Left Trigger 0", + "GamepadSingleRightTrigger0": "Right Trigger 0", + "GamepadSingleLeftTrigger1": "Left Trigger 1", + "GamepadSingleRightTrigger1": "Right Trigger 1", + "StickLeft": "Left Stick", + "StickRight": "Right Stick", "UserProfilesSelectedUserProfile": "פרופיל המשתמש הנבחר:", "UserProfilesSaveProfileName": "שמור שם פרופיל", "UserProfilesChangeProfileImage": "שנה תמונת פרופיל", @@ -597,6 +703,7 @@ "UserProfileWindowTitle": "ניהול פרופילי משתמש", "CheatWindowTitle": "נהל צ'יטים למשחק", "DlcWindowTitle": "נהל הרחבות משחק עבור {0} ({1})", + "ModWindowTitle": "Manage Mods for {0} ({1})", "UpdateWindowTitle": "נהל עדכוני משחקים", "CheatWindowHeading": "צ'יטים זמינים עבור {0} [{1}]", "BuildId": "מזהה בניה:", diff --git a/src/Ryujinx/Assets/Locales/it_IT.json b/src/Ryujinx/Assets/Locales/it_IT.json index 0e832ffb6c..280ebd880b 100644 --- a/src/Ryujinx/Assets/Locales/it_IT.json +++ b/src/Ryujinx/Assets/Locales/it_IT.json @@ -2,7 +2,7 @@ "Language": "Italiano", "MenuBarFileOpenApplet": "Apri applet", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Apri l'applet Mii Editor in modalità Standalone", - "SettingsTabInputDirectMouseAccess": "Accesso diretto mouse", + "SettingsTabInputDirectMouseAccess": "Accesso diretto al mouse", "SettingsTabSystemMemoryManagerMode": "Modalità di gestione della memoria:", "SettingsTabSystemMemoryManagerModeSoftware": "Software", "SettingsTabSystemMemoryManagerModeHost": "Host (veloce)", @@ -30,6 +30,10 @@ "MenuBarToolsManageFileTypes": "Gestisci i tipi di file", "MenuBarToolsInstallFileTypes": "Installa i tipi di file", "MenuBarToolsUninstallFileTypes": "Disinstalla i tipi di file", + "MenuBarView": "_View", + "MenuBarViewWindow": "Window Size", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", "MenuBarHelp": "_Aiuto", "MenuBarHelpCheckForUpdates": "Controlla aggiornamenti", "MenuBarHelpAbout": "Informazioni", @@ -92,6 +96,7 @@ "SettingsTabGeneralEnableDiscordRichPresence": "Attiva Discord Rich Presence", "SettingsTabGeneralCheckUpdatesOnLaunch": "Controlla aggiornamenti all'avvio", "SettingsTabGeneralShowConfirmExitDialog": "Mostra dialogo \"Conferma Uscita\"", + "SettingsTabGeneralRememberWindowState": "Remember Window Size/Position", "SettingsTabGeneralHideCursor": "Nascondi il cursore:", "SettingsTabGeneralHideCursorNever": "Mai", "SettingsTabGeneralHideCursorOnIdle": "Quando è inattivo", @@ -266,6 +271,107 @@ "ControllerSettingsMotionGyroDeadzone": "Zona morta del giroscopio:", "ControllerSettingsSave": "Salva", "ControllerSettingsClose": "Chiudi", + "KeyUnknown": "Sconosciuto", + "KeyShiftLeft": "Maiusc sinistro", + "KeyShiftRight": "Maiusc destro", + "KeyControlLeft": "Ctrl sinistro", + "KeyMacControlLeft": "⌃ sinistro", + "KeyControlRight": "Ctrl destro", + "KeyMacControlRight": "⌃ destro", + "KeyAltLeft": "Alt sinistro", + "KeyMacAltLeft": "⌥ sinistro", + "KeyAltRight": "Alt destro", + "KeyMacAltRight": "⌥ destro", + "KeyWinLeft": "⊞ sinistro", + "KeyMacWinLeft": "⌘ sinistro", + "KeyWinRight": "⊞ destro", + "KeyMacWinRight": "⌘ destro", + "KeyMenu": "Menù", + "KeyUp": "Su", + "KeyDown": "Giù", + "KeyLeft": "Sinistra", + "KeyRight": "Destra", + "KeyEnter": "Invio", + "KeyEscape": "Esc", + "KeySpace": "Spazio", + "KeyTab": "Tab", + "KeyBackSpace": "Backspace", + "KeyInsert": "Ins", + "KeyDelete": "Canc", + "KeyPageUp": "Pag. Su", + "KeyPageDown": "Pag. Giù", + "KeyHome": "Inizio", + "KeyEnd": "Fine", + "KeyCapsLock": "Bloc Maiusc", + "KeyScrollLock": "Bloc Scorr", + "KeyPrintScreen": "Stamp", + "KeyPause": "Pausa", + "KeyNumLock": "Bloc Num", + "KeyClear": "Clear", + "KeyKeypad0": "Tast. num. 0", + "KeyKeypad1": "Tast. num. 1", + "KeyKeypad2": "Tast. num. 2", + "KeyKeypad3": "Tast. num. 3", + "KeyKeypad4": "Tast. num. 4", + "KeyKeypad5": "Tast. num. 5", + "KeyKeypad6": "Tast. num. 6", + "KeyKeypad7": "Tast. num. 7", + "KeyKeypad8": "Tast. num. 8", + "KeyKeypad9": "Tast. num. 9", + "KeyKeypadDivide": "Tast. num. /", + "KeyKeypadMultiply": "Tast. num. *", + "KeyKeypadSubtract": "Tast. num. -", + "KeyKeypadAdd": "Tast. num. +", + "KeyKeypadDecimal": "Tast. num. sep. decimale", + "KeyKeypadEnter": "Tast. num. Invio", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "ò", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "'", + "KeyBracketRight": "ì", + "KeySemicolon": "è", + "KeyQuote": "à", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "ù", + "KeyBackSlash": "<", + "KeyUnbound": "Non assegnato", + "GamepadLeftStick": "Pulsante levetta sinistra", + "GamepadRightStick": "Pulsante levetta destra", + "GamepadLeftShoulder": "Pulsante dorsale sinistro", + "GamepadRightShoulder": "Pulsante dorsale destro", + "GamepadLeftTrigger": "Grilletto sinistro", + "GamepadRightTrigger": "Grilletto destro", + "GamepadDpadUp": "Su", + "GamepadDpadDown": "Giù", + "GamepadDpadLeft": "Sinistra", + "GamepadDpadRight": "Destra", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "Guide", + "GamepadMisc1": "Misc", + "GamepadPaddle1": "Paddle 1", + "GamepadPaddle2": "Paddle 2", + "GamepadPaddle3": "Paddle 3", + "GamepadPaddle4": "Paddle 4", + "GamepadTouchpad": "Touchpad", + "GamepadSingleLeftTrigger0": "Grilletto sinistro 0", + "GamepadSingleRightTrigger0": "Grilletto destro 0", + "GamepadSingleLeftTrigger1": "Grilletto sinistro 1", + "GamepadSingleRightTrigger1": "Grilletto destro 1", + "StickLeft": "Levetta sinistra", + "StickRight": "Levetta destra", "UserProfilesSelectedUserProfile": "Profilo utente selezionato:", "UserProfilesSaveProfileName": "Salva nome del profilo", "UserProfilesChangeProfileImage": "Cambia immagine profilo", @@ -597,6 +703,7 @@ "UserProfileWindowTitle": "Gestione profili utente", "CheatWindowTitle": "Gestione trucchi", "DlcWindowTitle": "Gestisci DLC per {0} ({1})", + "ModWindowTitle": "Gestisci mod per {0} ({1})", "UpdateWindowTitle": "Gestione aggiornamenti", "CheatWindowHeading": "Trucchi disponibili per {0} [{1}]", "BuildId": "ID Build", diff --git a/src/Ryujinx/Assets/Locales/ja_JP.json b/src/Ryujinx/Assets/Locales/ja_JP.json index 8790135e01..61e9632581 100644 --- a/src/Ryujinx/Assets/Locales/ja_JP.json +++ b/src/Ryujinx/Assets/Locales/ja_JP.json @@ -30,6 +30,10 @@ "MenuBarToolsManageFileTypes": "ファイル形式を管理", "MenuBarToolsInstallFileTypes": "ファイル形式をインストール", "MenuBarToolsUninstallFileTypes": "ファイル形式をアンインストール", + "MenuBarView": "_View", + "MenuBarViewWindow": "Window Size", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", "MenuBarHelp": "ヘルプ(_H)", "MenuBarHelpCheckForUpdates": "アップデートを確認", "MenuBarHelpAbout": "Ryujinx について", @@ -92,6 +96,7 @@ "SettingsTabGeneralEnableDiscordRichPresence": "Discord リッチプレゼンスを有効にする", "SettingsTabGeneralCheckUpdatesOnLaunch": "起動時にアップデートを確認する", "SettingsTabGeneralShowConfirmExitDialog": "\"終了を確認\" ダイアログを表示する", + "SettingsTabGeneralRememberWindowState": "Remember Window Size/Position", "SettingsTabGeneralHideCursor": "マウスカーソルを非表示", "SettingsTabGeneralHideCursorNever": "決して", "SettingsTabGeneralHideCursorOnIdle": "アイドル時", @@ -266,6 +271,107 @@ "ControllerSettingsMotionGyroDeadzone": "ジャイロ遊び:", "ControllerSettingsSave": "セーブ", "ControllerSettingsClose": "閉じる", + "KeyUnknown": "Unknown", + "KeyShiftLeft": "Shift Left", + "KeyShiftRight": "Shift Right", + "KeyControlLeft": "Ctrl Left", + "KeyMacControlLeft": "⌃ Left", + "KeyControlRight": "Ctrl Right", + "KeyMacControlRight": "⌃ Right", + "KeyAltLeft": "Alt Left", + "KeyMacAltLeft": "⌥ Left", + "KeyAltRight": "Alt Right", + "KeyMacAltRight": "⌥ Right", + "KeyWinLeft": "⊞ Left", + "KeyMacWinLeft": "⌘ Left", + "KeyWinRight": "⊞ Right", + "KeyMacWinRight": "⌘ Right", + "KeyMenu": "Menu", + "KeyUp": "Up", + "KeyDown": "Down", + "KeyLeft": "Left", + "KeyRight": "Right", + "KeyEnter": "Enter", + "KeyEscape": "Escape", + "KeySpace": "Space", + "KeyTab": "Tab", + "KeyBackSpace": "Backspace", + "KeyInsert": "Insert", + "KeyDelete": "Delete", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "Print Screen", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "Clear", + "KeyKeypad0": "Keypad 0", + "KeyKeypad1": "Keypad 1", + "KeyKeypad2": "Keypad 2", + "KeyKeypad3": "Keypad 3", + "KeyKeypad4": "Keypad 4", + "KeyKeypad5": "Keypad 5", + "KeyKeypad6": "Keypad 6", + "KeyKeypad7": "Keypad 7", + "KeyKeypad8": "Keypad 8", + "KeyKeypad9": "Keypad 9", + "KeyKeypadDivide": "Keypad Divide", + "KeyKeypadMultiply": "Keypad Multiply", + "KeyKeypadSubtract": "Keypad Subtract", + "KeyKeypadAdd": "Keypad Add", + "KeyKeypadDecimal": "Keypad Decimal", + "KeyKeypadEnter": "Keypad Enter", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "Unbound", + "GamepadLeftStick": "L Stick Button", + "GamepadRightStick": "R Stick Button", + "GamepadLeftShoulder": "Left Shoulder", + "GamepadRightShoulder": "Right Shoulder", + "GamepadLeftTrigger": "Left Trigger", + "GamepadRightTrigger": "Right Trigger", + "GamepadDpadUp": "Up", + "GamepadDpadDown": "Down", + "GamepadDpadLeft": "Left", + "GamepadDpadRight": "Right", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "Guide", + "GamepadMisc1": "Misc", + "GamepadPaddle1": "Paddle 1", + "GamepadPaddle2": "Paddle 2", + "GamepadPaddle3": "Paddle 3", + "GamepadPaddle4": "Paddle 4", + "GamepadTouchpad": "Touchpad", + "GamepadSingleLeftTrigger0": "Left Trigger 0", + "GamepadSingleRightTrigger0": "Right Trigger 0", + "GamepadSingleLeftTrigger1": "Left Trigger 1", + "GamepadSingleRightTrigger1": "Right Trigger 1", + "StickLeft": "Left Stick", + "StickRight": "Right Stick", "UserProfilesSelectedUserProfile": "選択されたユーザプロファイル:", "UserProfilesSaveProfileName": "プロファイル名をセーブ", "UserProfilesChangeProfileImage": "プロファイル画像を変更", @@ -597,6 +703,7 @@ "UserProfileWindowTitle": "ユーザプロファイルを管理", "CheatWindowTitle": "チート管理", "DlcWindowTitle": "DLC 管理", + "ModWindowTitle": "Manage Mods for {0} ({1})", "UpdateWindowTitle": "アップデート管理", "CheatWindowHeading": "利用可能なチート {0} [{1}]", "BuildId": "ビルドID:", diff --git a/src/Ryujinx/Assets/Locales/ko_KR.json b/src/Ryujinx/Assets/Locales/ko_KR.json index 0cd054cab2..a92d084e01 100644 --- a/src/Ryujinx/Assets/Locales/ko_KR.json +++ b/src/Ryujinx/Assets/Locales/ko_KR.json @@ -30,6 +30,10 @@ "MenuBarToolsManageFileTypes": "파일 형식 관리", "MenuBarToolsInstallFileTypes": "파일 형식 설치", "MenuBarToolsUninstallFileTypes": "파일 형식 설치 제거", + "MenuBarView": "_보기", + "MenuBarViewWindow": "창 크기", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", "MenuBarHelp": "도움말(_H)", "MenuBarHelpCheckForUpdates": "업데이트 확인", "MenuBarHelpAbout": "정보", @@ -92,6 +96,7 @@ "SettingsTabGeneralEnableDiscordRichPresence": "디스코드 활동 상태 활성화", "SettingsTabGeneralCheckUpdatesOnLaunch": "시작 시, 업데이트 확인", "SettingsTabGeneralShowConfirmExitDialog": "\"종료 확인\" 대화 상자 표시", + "SettingsTabGeneralRememberWindowState": "창 크기/위치 기억", "SettingsTabGeneralHideCursor": "마우스 커서 숨기기", "SettingsTabGeneralHideCursorNever": "절대 안 함", "SettingsTabGeneralHideCursorOnIdle": "유휴 상태", @@ -266,6 +271,107 @@ "ControllerSettingsMotionGyroDeadzone": "자이로 사각지대 :", "ControllerSettingsSave": "저장", "ControllerSettingsClose": "닫기", + "KeyUnknown": "알 수 없음", + "KeyShiftLeft": "왼쪽 Shift", + "KeyShiftRight": "오른쪽 Shift", + "KeyControlLeft": "왼쪽 Ctrl", + "KeyMacControlLeft": "왼쪽 ^", + "KeyControlRight": "오른쪽 Ctrl", + "KeyMacControlRight": "오른쪽 ^", + "KeyAltLeft": "왼쪽 Alt", + "KeyMacAltLeft": "왼쪽 ⌥", + "KeyAltRight": "오른쪽 Alt", + "KeyMacAltRight": "오른쪽 ⌥", + "KeyWinLeft": "왼쪽 ⊞", + "KeyMacWinLeft": "왼쪽 ⌘", + "KeyWinRight": "오른쪽 ⊞", + "KeyMacWinRight": "오른쪽 ⌘", + "KeyMenu": "메뉴", + "KeyUp": "↑", + "KeyDown": "↓", + "KeyLeft": "←", + "KeyRight": "→", + "KeyEnter": "엔터", + "KeyEscape": "이스케이프", + "KeySpace": "스페이스", + "KeyTab": "탭", + "KeyBackSpace": "백스페이스", + "KeyInsert": "Ins", + "KeyDelete": "Del", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "프린트 스크린", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "지우기", + "KeyKeypad0": "키패드 0", + "KeyKeypad1": "키패드 1", + "KeyKeypad2": "키패드 2", + "KeyKeypad3": "키패드 3", + "KeyKeypad4": "키패드 4", + "KeyKeypad5": "키패드 5", + "KeyKeypad6": "키패드 6", + "KeyKeypad7": "키패드 7", + "KeyKeypad8": "키패드 8", + "KeyKeypad9": "키패드 9", + "KeyKeypadDivide": "키패드 분할", + "KeyKeypadMultiply": "키패드 멀티플", + "KeyKeypadSubtract": "키패드 빼기", + "KeyKeypadAdd": "키패드 추가", + "KeyKeypadDecimal": "숫자 키패드", + "KeyKeypadEnter": "키패드 엔터", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "바인딩 해제", + "GamepadLeftStick": "L 스틱 버튼", + "GamepadRightStick": "R 스틱 버튼", + "GamepadLeftShoulder": "좌측 숄더", + "GamepadRightShoulder": "우측 숄더", + "GamepadLeftTrigger": "좌측 트리거", + "GamepadRightTrigger": "우측 트리거", + "GamepadDpadUp": "↑", + "GamepadDpadDown": "↓", + "GamepadDpadLeft": "←", + "GamepadDpadRight": "→", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "안내", + "GamepadMisc1": "기타", + "GamepadPaddle1": "패들 1", + "GamepadPaddle2": "패들 2", + "GamepadPaddle3": "패들 3", + "GamepadPaddle4": "패들 4", + "GamepadTouchpad": "터치패드", + "GamepadSingleLeftTrigger0": "왼쪽 트리거 0", + "GamepadSingleRightTrigger0": "오른쪽 트리거 0", + "GamepadSingleLeftTrigger1": "왼쪽 트리거 1", + "GamepadSingleRightTrigger1": "오른쪽 트리거 1", + "StickLeft": "좌측 스틱", + "StickRight": "우측 스틱", "UserProfilesSelectedUserProfile": "선택한 사용자 프로필 :", "UserProfilesSaveProfileName": "프로필 이름 저장", "UserProfilesChangeProfileImage": "프로필 이미지 변경", @@ -597,6 +703,7 @@ "UserProfileWindowTitle": "사용자 프로파일 관리자", "CheatWindowTitle": "치트 관리자", "DlcWindowTitle": "{0} ({1})의 다운로드 가능한 콘텐츠 관리", + "ModWindowTitle": "{0} ({1})의 Mod 관리", "UpdateWindowTitle": "타이틀 업데이트 관리자", "CheatWindowHeading": "{0} [{1}]에 사용할 수 있는 치트", "BuildId": "빌드ID :", diff --git a/src/Ryujinx/Assets/Locales/pl_PL.json b/src/Ryujinx/Assets/Locales/pl_PL.json index 94096dd7b4..9d1bd7b447 100644 --- a/src/Ryujinx/Assets/Locales/pl_PL.json +++ b/src/Ryujinx/Assets/Locales/pl_PL.json @@ -30,6 +30,10 @@ "MenuBarToolsManageFileTypes": "Zarządzaj rodzajami plików", "MenuBarToolsInstallFileTypes": "Typy plików instalacyjnych", "MenuBarToolsUninstallFileTypes": "Typy plików dezinstalacyjnych", + "MenuBarView": "_View", + "MenuBarViewWindow": "Window Size", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", "MenuBarHelp": "_Pomoc", "MenuBarHelpCheckForUpdates": "Sprawdź aktualizacje", "MenuBarHelpAbout": "O programie", @@ -92,6 +96,7 @@ "SettingsTabGeneralEnableDiscordRichPresence": "Włącz Bogatą Obecność Discord", "SettingsTabGeneralCheckUpdatesOnLaunch": "Sprawdzaj aktualizacje przy uruchomieniu", "SettingsTabGeneralShowConfirmExitDialog": "Pokazuj okno dialogowe \"Potwierdź wyjście\"", + "SettingsTabGeneralRememberWindowState": "Remember Window Size/Position", "SettingsTabGeneralHideCursor": "Ukryj kursor:", "SettingsTabGeneralHideCursorNever": "Nigdy", "SettingsTabGeneralHideCursorOnIdle": "Gdy bezczynny", @@ -266,6 +271,107 @@ "ControllerSettingsMotionGyroDeadzone": "Deadzone Żyroskopu:", "ControllerSettingsSave": "Zapisz", "ControllerSettingsClose": "Zamknij", + "KeyUnknown": "Unknown", + "KeyShiftLeft": "Shift Left", + "KeyShiftRight": "Shift Right", + "KeyControlLeft": "Ctrl Left", + "KeyMacControlLeft": "⌃ Left", + "KeyControlRight": "Ctrl Right", + "KeyMacControlRight": "⌃ Right", + "KeyAltLeft": "Alt Left", + "KeyMacAltLeft": "⌥ Left", + "KeyAltRight": "Alt Right", + "KeyMacAltRight": "⌥ Right", + "KeyWinLeft": "⊞ Left", + "KeyMacWinLeft": "⌘ Left", + "KeyWinRight": "⊞ Right", + "KeyMacWinRight": "⌘ Right", + "KeyMenu": "Menu", + "KeyUp": "Up", + "KeyDown": "Down", + "KeyLeft": "Left", + "KeyRight": "Right", + "KeyEnter": "Enter", + "KeyEscape": "Escape", + "KeySpace": "Space", + "KeyTab": "Tab", + "KeyBackSpace": "Backspace", + "KeyInsert": "Insert", + "KeyDelete": "Delete", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "Print Screen", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "Clear", + "KeyKeypad0": "Keypad 0", + "KeyKeypad1": "Keypad 1", + "KeyKeypad2": "Keypad 2", + "KeyKeypad3": "Keypad 3", + "KeyKeypad4": "Keypad 4", + "KeyKeypad5": "Keypad 5", + "KeyKeypad6": "Keypad 6", + "KeyKeypad7": "Keypad 7", + "KeyKeypad8": "Keypad 8", + "KeyKeypad9": "Keypad 9", + "KeyKeypadDivide": "Keypad Divide", + "KeyKeypadMultiply": "Keypad Multiply", + "KeyKeypadSubtract": "Keypad Subtract", + "KeyKeypadAdd": "Keypad Add", + "KeyKeypadDecimal": "Keypad Decimal", + "KeyKeypadEnter": "Keypad Enter", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "Unbound", + "GamepadLeftStick": "L Stick Button", + "GamepadRightStick": "R Stick Button", + "GamepadLeftShoulder": "Left Shoulder", + "GamepadRightShoulder": "Right Shoulder", + "GamepadLeftTrigger": "Left Trigger", + "GamepadRightTrigger": "Right Trigger", + "GamepadDpadUp": "Up", + "GamepadDpadDown": "Down", + "GamepadDpadLeft": "Left", + "GamepadDpadRight": "Right", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "Guide", + "GamepadMisc1": "Misc", + "GamepadPaddle1": "Paddle 1", + "GamepadPaddle2": "Paddle 2", + "GamepadPaddle3": "Paddle 3", + "GamepadPaddle4": "Paddle 4", + "GamepadTouchpad": "Touchpad", + "GamepadSingleLeftTrigger0": "Left Trigger 0", + "GamepadSingleRightTrigger0": "Right Trigger 0", + "GamepadSingleLeftTrigger1": "Left Trigger 1", + "GamepadSingleRightTrigger1": "Right Trigger 1", + "StickLeft": "Left Stick", + "StickRight": "Right Stick", "UserProfilesSelectedUserProfile": "Wybrany profil użytkownika:", "UserProfilesSaveProfileName": "Zapisz nazwę profilu", "UserProfilesChangeProfileImage": "Zmień obrazek profilu", @@ -341,7 +447,7 @@ "DialogThemeRestartMessage": "Motyw został zapisany. Aby zastosować motyw, konieczne jest ponowne uruchomienie.", "DialogThemeRestartSubMessage": "Czy chcesz uruchomić ponownie?", "DialogFirmwareInstallEmbeddedMessage": "Czy chcesz zainstalować firmware wbudowany w tę grę? (Firmware {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "No installed firmware was found but Ryujinx was able to install firmware {0} from the provided game.\nThe emulator will now start.", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Nie znaleziono zainstalowanego oprogramowania, ale Ryujinx był w stanie zainstalować oprogramowanie {0} z dostarczonej gry.\n\nEmulator uruchomi się teraz.", "DialogFirmwareNoFirmwareInstalledMessage": "Brak Zainstalowanego Firmware'u", "DialogFirmwareInstalledMessage": "Firmware {0} został zainstalowany", "DialogInstallFileTypesSuccessMessage": "Pomyślnie zainstalowano typy plików!", @@ -597,6 +703,7 @@ "UserProfileWindowTitle": "Menedżer Profili Użytkowników", "CheatWindowTitle": "Menedżer Kodów", "DlcWindowTitle": "Menedżer Zawartości do Pobrania", + "ModWindowTitle": "Zarządzaj modami dla {0} ({1})", "UpdateWindowTitle": "Menedżer Aktualizacji Tytułu", "CheatWindowHeading": "Kody Dostępne dla {0} [{1}]", "BuildId": "Identyfikator wersji:", @@ -648,11 +755,11 @@ "GraphicsAALabel": "Antyaliasing:", "GraphicsScalingFilterLabel": "Filtr skalowania:", "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", - "GraphicsScalingFilterBilinear": "Bilinear", - "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterBilinear": "Dwuliniowe", + "GraphicsScalingFilterNearest": "Najbliższe", "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "Poziom", - "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", + "GraphicsScalingFilterLevelTooltip": "Ustaw poziom ostrzeżenia FSR 1.0. Wyższy jest ostrzejszy.", "SmaaLow": "SMAA Niskie", "SmaaMedium": "SMAA Średnie", "SmaaHigh": "SMAA Wysokie", @@ -660,7 +767,7 @@ "UserEditorTitle": "Edytuj użytkownika", "UserEditorTitleCreate": "Utwórz użytkownika", "SettingsTabNetworkInterface": "Interfejs sieci:", - "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", + "NetworkInterfaceTooltip": "Interfejs sieciowy używany dla funkcji LAN/LDN.\n\nw połączeniu z VPN lub XLink Kai i grą z obsługą sieci LAN, może być użyty do spoofowania połączenia z tą samą siecią przez Internet.\n\nZostaw DOMYŚLNE, jeśli nie ma pewności.", "NetworkInterfaceDefault": "Domyślny", "PackagingShaders": "Pakuje Shadery ", "AboutChangelogButton": "Zobacz listę zmian na GitHubie", @@ -668,6 +775,6 @@ "SettingsTabNetworkMultiplayer": "Gra Wieloosobowa", "MultiplayerMode": "Tryb:", "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.", - "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeDisabled": "Wyłączone", "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/pt_BR.json b/src/Ryujinx/Assets/Locales/pt_BR.json index fb48bd5937..a8c244b650 100644 --- a/src/Ryujinx/Assets/Locales/pt_BR.json +++ b/src/Ryujinx/Assets/Locales/pt_BR.json @@ -30,6 +30,10 @@ "MenuBarToolsManageFileTypes": "Gerenciar tipos de arquivo", "MenuBarToolsInstallFileTypes": "Instalar tipos de arquivo", "MenuBarToolsUninstallFileTypes": "Desinstalar tipos de arquivos", + "MenuBarView": "_View", + "MenuBarViewWindow": "Window Size", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", "MenuBarHelp": "_Ajuda", "MenuBarHelpCheckForUpdates": "_Verificar se há atualizações", "MenuBarHelpAbout": "_Sobre", @@ -92,6 +96,7 @@ "SettingsTabGeneralEnableDiscordRichPresence": "Habilitar Rich Presence do Discord", "SettingsTabGeneralCheckUpdatesOnLaunch": "Verificar se há atualizações ao iniciar", "SettingsTabGeneralShowConfirmExitDialog": "Exibir diálogo de confirmação ao sair", + "SettingsTabGeneralRememberWindowState": "Lembrar tamanho/posição da Janela", "SettingsTabGeneralHideCursor": "Esconder o cursor do mouse:", "SettingsTabGeneralHideCursorNever": "Nunca", "SettingsTabGeneralHideCursorOnIdle": "Esconder o cursor quando ocioso", @@ -266,6 +271,107 @@ "ControllerSettingsMotionGyroDeadzone": "Zona morta do giroscópio:", "ControllerSettingsSave": "Salvar", "ControllerSettingsClose": "Fechar", + "KeyUnknown": "Unknown", + "KeyShiftLeft": "Shift Left", + "KeyShiftRight": "Shift Right", + "KeyControlLeft": "Ctrl Left", + "KeyMacControlLeft": "⌃ Left", + "KeyControlRight": "Ctrl Right", + "KeyMacControlRight": "⌃ Right", + "KeyAltLeft": "Alt Left", + "KeyMacAltLeft": "⌥ Left", + "KeyAltRight": "Alt Right", + "KeyMacAltRight": "⌥ Right", + "KeyWinLeft": "⊞ Left", + "KeyMacWinLeft": "⌘ Left", + "KeyWinRight": "⊞ Right", + "KeyMacWinRight": "⌘ Right", + "KeyMenu": "Menu", + "KeyUp": "Up", + "KeyDown": "Down", + "KeyLeft": "Left", + "KeyRight": "Right", + "KeyEnter": "Enter", + "KeyEscape": "Escape", + "KeySpace": "Space", + "KeyTab": "Tab", + "KeyBackSpace": "Backspace", + "KeyInsert": "Insert", + "KeyDelete": "Delete", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "Print Screen", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "Clear", + "KeyKeypad0": "Keypad 0", + "KeyKeypad1": "Keypad 1", + "KeyKeypad2": "Keypad 2", + "KeyKeypad3": "Keypad 3", + "KeyKeypad4": "Keypad 4", + "KeyKeypad5": "Keypad 5", + "KeyKeypad6": "Keypad 6", + "KeyKeypad7": "Keypad 7", + "KeyKeypad8": "Keypad 8", + "KeyKeypad9": "Keypad 9", + "KeyKeypadDivide": "Keypad Divide", + "KeyKeypadMultiply": "Keypad Multiply", + "KeyKeypadSubtract": "Keypad Subtract", + "KeyKeypadAdd": "Keypad Add", + "KeyKeypadDecimal": "Keypad Decimal", + "KeyKeypadEnter": "Keypad Enter", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "Unbound", + "GamepadLeftStick": "L Stick Button", + "GamepadRightStick": "R Stick Button", + "GamepadLeftShoulder": "Left Shoulder", + "GamepadRightShoulder": "Right Shoulder", + "GamepadLeftTrigger": "Left Trigger", + "GamepadRightTrigger": "Right Trigger", + "GamepadDpadUp": "Up", + "GamepadDpadDown": "Down", + "GamepadDpadLeft": "Left", + "GamepadDpadRight": "Right", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "Guide", + "GamepadMisc1": "Misc", + "GamepadPaddle1": "Paddle 1", + "GamepadPaddle2": "Paddle 2", + "GamepadPaddle3": "Paddle 3", + "GamepadPaddle4": "Paddle 4", + "GamepadTouchpad": "Touchpad", + "GamepadSingleLeftTrigger0": "Left Trigger 0", + "GamepadSingleRightTrigger0": "Right Trigger 0", + "GamepadSingleLeftTrigger1": "Left Trigger 1", + "GamepadSingleRightTrigger1": "Right Trigger 1", + "StickLeft": "Left Stick", + "StickRight": "Right Stick", "UserProfilesSelectedUserProfile": "Perfil de usuário selecionado:", "UserProfilesSaveProfileName": "Salvar nome de perfil", "UserProfilesChangeProfileImage": "Mudar imagem de perfil", @@ -597,6 +703,7 @@ "UserProfileWindowTitle": "Gerenciador de perfis de usuário", "CheatWindowTitle": "Gerenciador de Cheats", "DlcWindowTitle": "Gerenciador de DLC", + "ModWindowTitle": "Manage Mods for {0} ({1})", "UpdateWindowTitle": "Gerenciador de atualizações", "CheatWindowHeading": "Cheats disponíveis para {0} [{1}]", "BuildId": "ID da Build", diff --git a/src/Ryujinx/Assets/Locales/ru_RU.json b/src/Ryujinx/Assets/Locales/ru_RU.json index 284b8e2b4a..75fd4fe129 100644 --- a/src/Ryujinx/Assets/Locales/ru_RU.json +++ b/src/Ryujinx/Assets/Locales/ru_RU.json @@ -1,9 +1,9 @@ { "Language": "Русский (RU)", "MenuBarFileOpenApplet": "Открыть апплет", - "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Открыть апплет Mii Editor в автономном режиме", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Открывает апплет Mii Editor в автономном режиме", "SettingsTabInputDirectMouseAccess": "Прямой ввод мыши", - "SettingsTabSystemMemoryManagerMode": "Режим диспетчера памяти:", + "SettingsTabSystemMemoryManagerMode": "Режим менеджера памяти:", "SettingsTabSystemMemoryManagerModeSoftware": "Программное обеспечение", "SettingsTabSystemMemoryManagerModeHost": "Хост (быстро)", "SettingsTabSystemMemoryManagerModeHostUnchecked": "Хост не установлен (самый быстрый, небезопасный)", @@ -30,6 +30,10 @@ "MenuBarToolsManageFileTypes": "Управление типами файлов", "MenuBarToolsInstallFileTypes": "Установить типы файлов", "MenuBarToolsUninstallFileTypes": "Удалить типы файлов", + "MenuBarView": "_Вид", + "MenuBarViewWindow": "Размер окна", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", "MenuBarHelp": "_Помощь", "MenuBarHelpCheckForUpdates": "Проверить наличие обновлений", "MenuBarHelpAbout": "О программе", @@ -40,7 +44,7 @@ "GameListHeaderDeveloper": "Разработчик", "GameListHeaderVersion": "Версия", "GameListHeaderTimePlayed": "Время в игре", - "GameListHeaderLastPlayed": "Последняя игра", + "GameListHeaderLastPlayed": "Последний запуск", "GameListHeaderFileExtension": "Расширение файла", "GameListHeaderFileSize": "Размер файла", "GameListHeaderPath": "Путь", @@ -80,7 +84,7 @@ "StatusBarGamesLoaded": "{0}/{1} игр загружено", "StatusBarSystemVersion": "Версия прошивки: {0}", "LinuxVmMaxMapCountDialogTitle": "Обнаружен низкий лимит разметки памяти", - "LinuxVmMaxMapCountDialogTextPrimary": "Вы хотите увеличить значение vm.max_map_count до {0}", + "LinuxVmMaxMapCountDialogTextPrimary": "Хотите увеличить значение vm.max_map_count до {0}", "LinuxVmMaxMapCountDialogTextSecondary": "Некоторые игры могут создавать большую разметку памяти, чем разрешено на данный момент по умолчанию. Ryujinx вылетит при превышении этого лимита.", "LinuxVmMaxMapCountDialogButtonUntilRestart": "Да, до следующего перезапуска", "LinuxVmMaxMapCountDialogButtonPersistent": "Да, постоянно", @@ -89,12 +93,13 @@ "Settings": "Параметры", "SettingsTabGeneral": "Интерфейс", "SettingsTabGeneralGeneral": "Общее", - "SettingsTabGeneralEnableDiscordRichPresence": "Включить статус активности в Discord", + "SettingsTabGeneralEnableDiscordRichPresence": "Статус активности в Discord", "SettingsTabGeneralCheckUpdatesOnLaunch": "Проверять наличие обновлений при запуске", "SettingsTabGeneralShowConfirmExitDialog": "Подтверждать выход из приложения", + "SettingsTabGeneralRememberWindowState": "Запомнить размер/положение окна", "SettingsTabGeneralHideCursor": "Скрывать курсор", "SettingsTabGeneralHideCursorNever": "Никогда", - "SettingsTabGeneralHideCursorOnIdle": "В режиме ожидания", + "SettingsTabGeneralHideCursorOnIdle": "В простое", "SettingsTabGeneralHideCursorAlways": "Всегда", "SettingsTabGeneralGameDirectories": "Папки с играми", "SettingsTabGeneralAdd": "Добавить", @@ -125,13 +130,13 @@ "SettingsTabSystemSystemLanguageBritishEnglish": "Английский (Британия)", "SettingsTabSystemSystemLanguageCanadianFrench": "Французский (Канада)", "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Испанский (Латинская Америка)", - "SettingsTabSystemSystemLanguageSimplifiedChinese": "Китайский упрощённый", - "SettingsTabSystemSystemLanguageTraditionalChinese": "Китайский традиционный", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Китайский (упрощённый)", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Китайский (традиционный)", "SettingsTabSystemSystemTimeZone": "Часовой пояс прошивки:", - "SettingsTabSystemSystemTime": "Время в прошивке:", - "SettingsTabSystemEnableVsync": "Включить вертикальную синхронизацию", - "SettingsTabSystemEnablePptc": "Включить PPTC (Profiled Persistent Translation Cache)", - "SettingsTabSystemEnableFsIntegrityChecks": "Включить проверку целостности файловой системы", + "SettingsTabSystemSystemTime": "Системное время в прошивке:", + "SettingsTabSystemEnableVsync": "Вертикальная синхронизация", + "SettingsTabSystemEnablePptc": "Использовать PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "Проверка целостности файловой системы", "SettingsTabSystemAudioBackend": "Аудио бэкенд:", "SettingsTabSystemAudioBackendDummy": "Без звука", "SettingsTabSystemAudioBackendOpenAL": "OpenAL", @@ -143,7 +148,7 @@ "SettingsTabSystemIgnoreMissingServices": "Игнорировать отсутствующие службы", "SettingsTabGraphics": "Графика", "SettingsTabGraphicsAPI": "Графические API", - "SettingsTabGraphicsEnableShaderCache": "Включить кэш шейдеров", + "SettingsTabGraphicsEnableShaderCache": "Кэшировать шейдеры", "SettingsTabGraphicsAnisotropicFiltering": "Анизотропная фильтрация:", "SettingsTabGraphicsAnisotropicFilteringAuto": "Автоматически", "SettingsTabGraphicsAnisotropicFiltering2x": "2x", @@ -164,7 +169,7 @@ "SettingsTabGraphicsAspectRatio32x9": "32:9", "SettingsTabGraphicsAspectRatioStretch": "Растянуть до размеров окна", "SettingsTabGraphicsDeveloperOptions": "Параметры разработчика", - "SettingsTabGraphicsShaderDumpPath": "Путь дампа графического шейдера:", + "SettingsTabGraphicsShaderDumpPath": "Путь дампа графических шейдеров", "SettingsTabLogging": "Журналирование", "SettingsTabLoggingLogging": "Журналирование", "SettingsTabLoggingEnableLoggingToFile": "Включить запись в файл", @@ -266,6 +271,107 @@ "ControllerSettingsMotionGyroDeadzone": "Мертвая зона гироскопа:", "ControllerSettingsSave": "Сохранить", "ControllerSettingsClose": "Закрыть", + "KeyUnknown": "Неизвестно", + "KeyShiftLeft": "Левый Shift", + "KeyShiftRight": "Правый Shift", + "KeyControlLeft": "Левый Ctrl", + "KeyMacControlLeft": "Левый ⌃", + "KeyControlRight": "Правый Ctrl", + "KeyMacControlRight": "Правый ⌃", + "KeyAltLeft": "Левый Alt", + "KeyMacAltLeft": "Левый ⌥", + "KeyAltRight": "Правый Alt", + "KeyMacAltRight": "Правый ⌥", + "KeyWinLeft": "Левый ⊞", + "KeyMacWinLeft": "Левый ⌘", + "KeyWinRight": "Правый ⊞", + "KeyMacWinRight": "Правый ⌘", + "KeyMenu": "Меню", + "KeyUp": "Вверх", + "KeyDown": "Вниз", + "KeyLeft": "Влево", + "KeyRight": "Вправо", + "KeyEnter": "Enter", + "KeyEscape": "Escape", + "KeySpace": "Пробел", + "KeyTab": "Tab", + "KeyBackSpace": "Backspace", + "KeyInsert": "Insert", + "KeyDelete": "Delete", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "Print Screen", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "Очистить", + "KeyKeypad0": "Блок цифр 0", + "KeyKeypad1": "Блок цифр 1", + "KeyKeypad2": "Блок цифр 2", + "KeyKeypad3": "Блок цифр 3", + "KeyKeypad4": "Блок цифр 4", + "KeyKeypad5": "Блок цифр 5", + "KeyKeypad6": "Блок цифр 6", + "KeyKeypad7": "Блок цифр 7", + "KeyKeypad8": "Блок цифр 8", + "KeyKeypad9": "Блок цифр 9", + "KeyKeypadDivide": "/ (блок цифр)", + "KeyKeypadMultiply": "* (блок цифр)", + "KeyKeypadSubtract": "- (блок цифр)", + "KeyKeypadAdd": "+ (блок цифр)", + "KeyKeypadDecimal": ". (блок цифр)", + "KeyKeypadEnter": "Enter (блок цифр)", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "Не привязано", + "GamepadLeftStick": "Кнопка лев. стика", + "GamepadRightStick": "Кнопка пр. стика", + "GamepadLeftShoulder": "Левый бампер", + "GamepadRightShoulder": "Правый бампер", + "GamepadLeftTrigger": "Левый триггер", + "GamepadRightTrigger": "Правый триггер", + "GamepadDpadUp": "Вверх", + "GamepadDpadDown": "Вниз", + "GamepadDpadLeft": "Влево", + "GamepadDpadRight": "Вправо", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "Кнопка Xbox", + "GamepadMisc1": "Прочее", + "GamepadPaddle1": "Доп.кнопка 1", + "GamepadPaddle2": "Доп.кнопка 2", + "GamepadPaddle3": "Доп.кнопка 3", + "GamepadPaddle4": "Доп.кнопка 4", + "GamepadTouchpad": "Тачпад", + "GamepadSingleLeftTrigger0": "Левый триггер 0", + "GamepadSingleRightTrigger0": "Правый триггер 0", + "GamepadSingleLeftTrigger1": "Левый триггер 1", + "GamepadSingleRightTrigger1": "Правый триггер 1", + "StickLeft": "Левый стик", + "StickRight": "Правый стик", "UserProfilesSelectedUserProfile": "Выбранный пользовательский профиль:", "UserProfilesSaveProfileName": "Сохранить пользовательский профиль", "UserProfilesChangeProfileImage": "Изменить аватар", @@ -273,17 +379,17 @@ "UserProfilesAddNewProfile": "Добавить новый профиль", "UserProfilesDelete": "Удалить", "UserProfilesClose": "Закрыть", - "ProfileNameSelectionWatermark": "Выберите никнейм", + "ProfileNameSelectionWatermark": "Укажите никнейм", "ProfileImageSelectionTitle": "Выбор изображения профиля", - "ProfileImageSelectionHeader": "Выберите аватар", + "ProfileImageSelectionHeader": "Выбор аватара", "ProfileImageSelectionNote": "Вы можете импортировать собственное изображение или выбрать аватар из системной прошивки.", "ProfileImageSelectionImportImage": "Импорт изображения", "ProfileImageSelectionSelectAvatar": "Встроенные аватары", "InputDialogTitle": "Диалоговое окно ввода", "InputDialogOk": "ОК", "InputDialogCancel": "Отмена", - "InputDialogAddNewProfileTitle": "Выберите имя профиля", - "InputDialogAddNewProfileHeader": "Пожалуйста, введите имя профиля", + "InputDialogAddNewProfileTitle": "Выберите никнейм", + "InputDialogAddNewProfileHeader": "Пожалуйста, введите никнейм", "InputDialogAddNewProfileSubtext": "(Максимальная длина: {0})", "AvatarChoose": "Выбрать аватар", "AvatarSetBackgroundColor": "Установить цвет фона", @@ -312,7 +418,7 @@ "DialogWarningTitle": "Ryujinx - Предупреждение", "DialogExitTitle": "Ryujinx - Выход", "DialogErrorMessage": "Ryujinx обнаружил ошибку", - "DialogExitMessage": "Вы уверены, что хотите закрыть Ryujinx?", + "DialogExitMessage": "Вы уверены, что хотите выйти из Ryujinx?", "DialogExitSubMessage": "Все несохраненные данные будут потеряны", "DialogMessageCreateSaveErrorMessage": "Произошла ошибка при создании указанных данных сохранения: {0}", "DialogMessageFindSaveErrorMessage": "Произошла ошибка при поиске указанных данных сохранения: {0}", @@ -324,7 +430,7 @@ "DialogNcaExtractionSuccessMessage": "Извлечение завершено успешно.", "DialogUpdaterConvertFailedMessage": "Не удалось преобразовать текущую версию Ryujinx.", "DialogUpdaterCancelUpdateMessage": "Отмена обновления...", - "DialogUpdaterAlreadyOnLatestVersionMessage": "Вы уже используете самую последнюю версию Ryujinx", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Вы используете самую последнюю версию Ryujinx", "DialogUpdaterFailedToGetVersionMessage": "Произошла ошибка при попытке получить информацию о выпуске от GitHub Release. Это может быть вызвано тем, что в данный момент в GitHub Actions компилируется новый релиз. Повторите попытку позже.", "DialogUpdaterConvertFailedGithubMessage": "Не удалось преобразовать полученную версию Ryujinx из Github Release.", "DialogUpdaterDownloadingMessage": "Загрузка обновления...", @@ -332,14 +438,14 @@ "DialogUpdaterRenamingMessage": "Переименование обновления...", "DialogUpdaterAddingFilesMessage": "Добавление нового обновления...", "DialogUpdaterCompleteMessage": "Обновление завершено", - "DialogUpdaterRestartMessage": "Вы хотите перезапустить Ryujinx сейчас?", + "DialogUpdaterRestartMessage": "Перезапустить Ryujinx?", "DialogUpdaterNoInternetMessage": "Вы не подключены к интернету", "DialogUpdaterNoInternetSubMessage": "Убедитесь, что у вас работает подключение к интернету", "DialogUpdaterDirtyBuildMessage": "Вы не можете обновлять Dirty Build", "DialogUpdaterDirtyBuildSubMessage": "Загрузите Ryujinx по адресу https://ryujinx.org/ если вам нужна поддерживаемая версия.", "DialogRestartRequiredMessage": "Требуется перезагрузка", "DialogThemeRestartMessage": "Тема сохранена. Для применения темы требуется перезапуск.", - "DialogThemeRestartSubMessage": "Вы хотите перезапустить?", + "DialogThemeRestartSubMessage": "Хотите перезапустить", "DialogFirmwareInstallEmbeddedMessage": "Хотите установить прошивку, встроенную в эту игру? (Прошивка {0})", "DialogFirmwareInstallEmbeddedSuccessMessage": "Установленная прошивка не была найдена, но Ryujinx удалось установить прошивку {0} из предоставленной игры.\nТеперь эмулятор запустится.", "DialogFirmwareNoFirmwareInstalledMessage": "Прошивка не установлена", @@ -348,7 +454,7 @@ "DialogInstallFileTypesErrorMessage": "Не удалось установить типы файлов.", "DialogUninstallFileTypesSuccessMessage": "Типы файлов успешно удалены", "DialogUninstallFileTypesErrorMessage": "Не удалось удалить типы файлов.", - "DialogOpenSettingsWindowLabel": "Открыть окно параметров", + "DialogOpenSettingsWindowLabel": "Открывает окно параметров", "DialogControllerAppletTitle": "Апплет контроллера", "DialogMessageDialogErrorExceptionMessage": "Ошибка отображения сообщения: {0}", "DialogSoftwareKeyboardErrorExceptionMessage": "Ошибка отображения программной клавиатуры: {0}", @@ -364,7 +470,7 @@ "DialogProfileDeleteProfileTitle": "Удаление профиля", "DialogProfileDeleteProfileMessage": "Это действие необратимо. Вы уверены, что хотите продолжить?", "DialogWarning": "Внимание", - "DialogPPTCDeletionMessage": "Вы собираетесь удалить кэш PPTC для:\n\n{0}\n\nВы уверены, что хотите продолжить?", + "DialogPPTCDeletionMessage": "Вы собираетесь перестроить кэш PPTC при следующем запуске для:\n\n{0}\n\nВы уверены, что хотите продолжить?", "DialogPPTCDeletionErrorMessage": "Ошибка очистки кэша PPTC в {0}: {1}", "DialogShaderDeletionMessage": "Вы собираетесь удалить кэш шейдеров для:\n\n{0}\n\nВы уверены, что хотите продолжить?", "DialogShaderDeletionErrorMessage": "Ошибка очистки кэша шейдеров в {0}: {1}", @@ -378,10 +484,10 @@ "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Установка прошивки...", "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Прошивка версии {0} успешно установлена.", "DialogUserProfileDeletionWarningMessage": "Если выбранный профиль будет удален, другие профили не будут открываться.", - "DialogUserProfileDeletionConfirmMessage": "Вы хотите удалить выбранный профиль", + "DialogUserProfileDeletionConfirmMessage": "Удалить выбранный профиль?", "DialogUserProfileUnsavedChangesTitle": "Внимание - Несохраненные изменения", - "DialogUserProfileUnsavedChangesMessage": "Вы внесли изменения в этот профиль пользователя которые не были сохранены.", - "DialogUserProfileUnsavedChangesSubMessage": "Вы хотите отменить изменения?", + "DialogUserProfileUnsavedChangesMessage": "В эту учетную запись внесены изменения, которые не были сохранены.", + "DialogUserProfileUnsavedChangesSubMessage": "Отменить изменения?", "DialogControllerSettingsModifiedConfirmMessage": "Текущие настройки управления обновлены.", "DialogControllerSettingsModifiedConfirmSubMessage": "Сохранить?", "DialogLoadFileErrorMessage": "{0}. Файл с ошибкой: {1}", @@ -444,40 +550,40 @@ "OrderDescending": "По убыванию", "SettingsTabGraphicsFeatures": "Функции", "ErrorWindowTitle": "Окно ошибки", - "ToggleDiscordTooltip": "Включает или отключает отображение в Discord статуса \"Сейчас играет\"", - "AddGameDirBoxTooltip": "Введите папку игры для добавления в список", + "ToggleDiscordTooltip": "Включает или отключает отображение статуса \"Играет в игру\" в Discord", + "AddGameDirBoxTooltip": "Введите путь к папке с играми для добавления ее в список выше", "AddGameDirTooltip": "Добавить папку с играми в список", "RemoveGameDirTooltip": "Удалить выбранную папку игры", "CustomThemeCheckTooltip": "Включить или отключить пользовательские темы", "CustomThemePathTooltip": "Путь к пользовательской теме для интерфейса", "CustomThemeBrowseTooltip": "Просмотр пользовательской темы интерфейса", - "DockModeToggleTooltip": "\"Стационарный\" режим запускает эмулятор, как если бы Nintendo Switch находилась в доке, что улучшает графику и разрешение в большинстве игр. И наоборот, при отключении этого режима эмулятор будет запускать игры в \"Портативном\" режиме, снижая качество графики.\n\nНастройте управление для Игрока 1 если планируете использовать в \"Стационарном\" режиме; настройте портативное управление если планируете использовать эмулятор в \"Портативном\" режиме.\n\nОставьте включенным если не уверены.", + "DockModeToggleTooltip": "\"Стационарный\" режим запускает эмулятор, как если бы Nintendo Switch находилась в доке, что улучшает графику и разрешение в большинстве игр. И наоборот, при отключении этого режима эмулятор будет запускать игры в \"Портативном\" режиме, снижая качество графики.\n\nНастройте управление для Игрока 1 если планируете использовать в \"Стационарном\" режиме; настройте портативное управление если планируете использовать эмулятор в \"Портативном\" режиме.\n\nРекомендуется оставить включенным.", "DirectKeyboardTooltip": "Поддержка прямого ввода с клавиатуры (HID). Предоставляет игре прямой доступ к клавиатуре в качестве устройства ввода текста.\nРаботает только с играми, которые изначально поддерживают использование клавиатуры с Switch.\nРекомендуется оставить выключенным.", "DirectMouseTooltip": "Поддержка прямого ввода мыши (HID). Предоставляет игре прямой доступ к мыши в качестве указывающего устройства.\nРаботает только с играми, которые изначально поддерживают использование мыши совместно с железом Switch.\nРекомендуется оставить выключенным.", - "RegionTooltip": "Изменение региона прошивки", - "LanguageTooltip": "Изменение языка прошивки", - "TimezoneTooltip": "Изменение часового пояса прошивки", - "TimeTooltip": "Изменение системного времени", + "RegionTooltip": "Сменяет регион прошивки", + "LanguageTooltip": "Меняет язык прошивки", + "TimezoneTooltip": "Меняет часовой пояс прошивки", + "TimeTooltip": "Меняет системное время прошивки", "VSyncToggleTooltip": "Эмуляция вертикальной синхронизации консоли, которая ограничивает количество кадров в секунду в большинстве игр; отключение может привести к тому, что игры будут запущены с более высокой частотой кадров, но загрузка игры может занять больше времени, либо игра не запустится вообще.\n\nМожно включать и выключать эту настройку непосредственно в игре с помощью горячих клавиш (F1 по умолчанию). Если планируете отключить вертикальную синхронизацию, рекомендуем настроить горячие клавиши.\n\nРекомендуется оставить включенным.", - "PptcToggleTooltip": "Сохранение преобразованных JIT-функций для того, чтобы не преобразовывать их по новой каждый раз при запуске игры.\n\nУменьшает статтеры и значительно ускоряет последующую загрузку игр.\n\nРекомендуется оставить включенным.", - "FsIntegrityToggleTooltip": "Проверяет поврежденные файлы при загрузке игры и если поврежденные файлы обнаружены, отображает ошибку о поврежденном хэше в журнале.\n\nНе влияет на производительность и необходим для помощи в устранении неполадок.\n\nРекомендуется оставить включенным.", - "AudioBackendTooltip": "Изменяет используемый аудио-бэкенд для рендера звука.\n\nSDL2 является предпочтительным, в то время как OpenAL и SoundIO используются в качестве резервных. \n\nРекомендуется использование SDL2.", - "MemoryManagerTooltip": "Изменение разметки и доступа к гостевой памяти. Значительно влияет на производительность процессора.\n\nРекомендуется оставить \"Хост не установлен\"", - "MemoryManagerSoftwareTooltip": "Использует таблицу страниц для преобразования адресов. Самая высокая точность, но самая низкая производительность.", - "MemoryManagerHostTooltip": "Прямая разметка памяти в адресном пространстве хоста. Значительно более быстрая JIT-компиляция и запуск.", - "MemoryManagerUnsafeTooltip": "Производит прямую разметку памяти, но не маскирует адрес в гостевом адресном пространстве перед получением доступа. Быстро, но небезопасно. Гостевое приложение может получить доступ к памяти из Ryujinx, поэтому в этом режиме рекомендуется запускать только те программы, которым вы доверяете.", + "PptcToggleTooltip": "Сохраняет скомпилированные JIT-функции для того, чтобы не преобразовывать их по новой каждый раз при запуске игры.\n\nУменьшает статтеры и значительно ускоряет последующую загрузку игр.\n\nРекомендуется оставить включенным.", + "FsIntegrityToggleTooltip": "Проверяет файлы при загрузке игры и если обнаружены поврежденные файлы, выводит сообщение о поврежденном хэше в журнале.\n\nНе влияет на производительность и необходим для помощи в устранении неполадок.\n\nРекомендуется оставить включенным.", + "AudioBackendTooltip": "Изменяет используемый аудио бэкенд для рендера звука.\n\nSDL2 является предпочтительным вариантом, в то время как OpenAL и SoundIO используются в качестве резервных.\n\nРекомендуется использование SDL2.", + "MemoryManagerTooltip": "Меняет разметку и доступ к гостевой памяти. Значительно влияет на производительность процессора.\n\nРекомендуется оставить \"Хост не установлен\"", + "MemoryManagerSoftwareTooltip": "Использует таблицу страниц для преобразования адресов. \nСамая высокая точность, но самая низкая производительность.", + "MemoryManagerHostTooltip": "Прямая разметка памяти в адресном пространстве хоста. \nЗначительно более быстрые запуск и компиляция JIT.", + "MemoryManagerUnsafeTooltip": "Производит прямую разметку памяти, но не маскирует адрес в гостевом адресном пространстве перед получением доступа. \nБыстро, но небезопасно. Гостевое приложение может получить доступ к памяти из Ryujinx, поэтому в этом режиме рекомендуется запускать только те программы, которым вы доверяете.", "UseHypervisorTooltip": "Использует Hypervisor вместо JIT. Значительно увеличивает производительность, но может работать нестабильно.", "DRamTooltip": "Использует альтернативный макет MemoryMode для имитации использования Nintendo Switch в режиме разработчика.\n\nПолезно только для пакетов текстур с высоким разрешением или модов добавляющих разрешение 4К. Не улучшает производительность.\n\nРекомендуется оставить выключенным.", "IgnoreMissingServicesTooltip": "Игнорирует нереализованные сервисы Horizon в новых прошивках. Эта настройка поможет избежать вылеты при запуске определенных игр.\n\nРекомендуется оставить выключенным.", - "GraphicsBackendThreadingTooltip": "Выполняет команды графического бэкенда на втором потоке.\n\nУскоряет компиляцию шейдеров, уменьшает статтеры и повышает производительность на драйверах GPU без поддержки многопоточности. Производительность на драйверах с многопоточностью немного выше.\n\nРекомендуется оставить Автоматически.", - "GalThreadingTooltip": "Выполняет команды графического бэкенда на втором потоке.\n\nУскоряет компиляцию шейдеров, уменьшает статтеры и повышает производительность на драйверах GPU без поддержки многопоточности. Производительность на драйверах с многопоточностью немного выше.\n\nРекомендуется оставить в Авто.", + "GraphicsBackendThreadingTooltip": "Выполняет команды графического бэкенда на втором потоке.\n\nУскоряет компиляцию шейдеров, уменьшает статтеры и повышает производительность на драйверах видеоадаптера без поддержки многопоточности. Производительность на драйверах с многопоточностью немного выше.\n\nРекомендуется оставить Автоматически.", + "GalThreadingTooltip": "Выполняет команды графического бэкенда на втором потоке.\n\nУскоряет компиляцию шейдеров, уменьшает статтеры и повышает производительность на драйверах видеоадаптера без поддержки многопоточности. Производительность на драйверах с многопоточностью немного выше.\n\nРекомендуется оставить Автоматически.", "ShaderCacheToggleTooltip": "Сохраняет кэш шейдеров на диске, для уменьшения статтеров при последующих запусках.\n\nРекомендуется оставить включенным.", - "ResolutionScaleTooltip": "Увеличивает разрешение рендера игры.\n\nНекоторые игры могут не работать с этой настройкой и выглядеть смазано даже когда разрешение увеличено; Для таких игр вам может потребоваться установить моды, которые убирают сглаживание или увеличивают разрешение рендеринга. Для использования последнего, вам нужно будет выбрать опцию \"Нативное\".\n\nЭта опция может быть изменена во время игры по нажатию кнопки \"Применить\" ниже; Вы можете просто переместить окно настроек в сторону и поэкспериментировать, пока не подберете подходящие настройки для конкретной игры.\n\nИмейте в виду, что \"4x\" является излишеством.", + "ResolutionScaleTooltip": "Увеличивает разрешение рендера игры.\n\nНекоторые игры могут не работать с этой настройкой и выглядеть смазано даже когда разрешение увеличено. Для таких игр может потребоваться установка модов, которые убирают сглаживание или увеличивают разрешение рендеринга. \nДля использования последнего, вам нужно будет выбрать опцию \"Нативное\".\n\nЭта опция может быть изменена во время игры по нажатию кнопки \"Применить\" ниже. Вы можете просто переместить окно настроек в сторону и поэкспериментировать, пока не подберете подходящие настройки для конкретной игры.\n\nИмейте в виду, что \"4x\" является излишеством.", "ResolutionScaleEntryTooltip": "Масштабирование разрешения с плавающей запятой, например 1,5. Неинтегральное масштабирование с большой вероятностью вызовет сбои в работе.", - "AnisotropyTooltip": "Уровень анизотропной фильтрации. Установите значение «Авто», чтобы использовать в игре значение по умолчанию игре.", + "AnisotropyTooltip": "Уровень анизотропной фильтрации. \n\nУстановите значение Автоматически, чтобы использовать значение по умолчанию игры.", "AspectRatioTooltip": "Соотношение сторон окна рендерера.\n\nИзмените эту настройку только если вы используете мод для соотношения сторон, иначе изображение будет растянуто.\n\nРекомендуется настройка 16:9.", "ShaderDumpPathTooltip": "Путь с дампами графических шейдеров", - "FileLogTooltip": "Включает или отключает ведение журнала в файл на диске. Не влияет на производительность.", + "FileLogTooltip": "Включает ведение журнала в файл на диске. Не влияет на производительность.", "StubLogTooltip": "Включает ведение журнала-заглушки. Не влияет на производительность.", "InfoLogTooltip": "Включает вывод сообщений информационного журнала в консоль. Не влияет на производительность.", "WarnLogTooltip": "Включает вывод сообщений журнала предупреждений в консоль. Не влияет на производительность.", @@ -489,42 +595,42 @@ "DeveloperOptionTooltip": "Используйте с осторожностью", "OpenGlLogLevel": "Требует включения соответствующих уровней ведения журнала", "DebugLogTooltip": "Выводит журнал сообщений отладки в консоли.\n\nИспользуйте только в случае просьбы разработчика, так как включение этой функции затруднит чтение журналов и ухудшит работу эмулятора.", - "LoadApplicationFileTooltip": "Открыть файловый менеджер для выбора файла, совместимого с Nintendo Switch.", - "LoadApplicationFolderTooltip": "Открыть файловый менеджер для выбора распакованного приложения, совместимого с Nintendo Switch.", + "LoadApplicationFileTooltip": "Открывает файловый менеджер для выбора файла, совместимого с Nintendo Switch.", + "LoadApplicationFolderTooltip": "Открывает файловый менеджер для выбора распакованного приложения, совместимого с Nintendo Switch.", "OpenRyujinxFolderTooltip": "Открывает папку с файлами Ryujinx. ", "OpenRyujinxLogsTooltip": "Открывает папку в которую записываются логи", "ExitTooltip": "Выйти из Ryujinx", - "OpenSettingsTooltip": "Открыть окно параметров", + "OpenSettingsTooltip": "Открывает окно параметров", "OpenProfileManagerTooltip": "Открыть менеджер учетных записей", "StopEmulationTooltip": "Остановка эмуляции текущей игры и возврат к списку игр", - "CheckUpdatesTooltip": "Проверка наличия обновления Ryujinx", - "OpenAboutTooltip": "Открыть окно «О программе»", + "CheckUpdatesTooltip": "Проверяет наличие обновлений для Ryujinx", + "OpenAboutTooltip": "Открывает окно «О программе»", "GridSize": "Размер сетки", - "GridSizeTooltip": "Изменение размера элементов сетки", + "GridSizeTooltip": "Меняет размер сетки элементов", "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Португальский язык (Бразилия)", "AboutRyujinxContributorsButtonHeader": "Посмотреть всех участников", "SettingsTabSystemAudioVolume": "Громкость: ", "AudioVolumeTooltip": "Изменяет громкость звука", - "SettingsTabSystemEnableInternetAccess": "Включить гостевой доступ в Интернет/сетевой режим", + "SettingsTabSystemEnableInternetAccess": "Гостевой доступ в интернет/сетевой режим", "EnableInternetAccessTooltip": "Позволяет эмулированному приложению подключаться к Интернету.\n\nПри включении этой функции игры с возможностью сетевой игры могут подключаться друг к другу, если все эмуляторы (или реальные консоли) подключены к одной и той же точке доступа.\n\nНЕ разрешает подключение к серверам Nintendo. Может вызвать сбой в некоторых играх, которые пытаются подключиться к Интернету.\n\nРекомендутеся оставить выключенным.", - "GameListContextMenuManageCheatToolTip": "Управление читами", + "GameListContextMenuManageCheatToolTip": "Открывает окно управления читами", "GameListContextMenuManageCheat": "Управление читами", - "GameListContextMenuManageModToolTip": "Управление модами", + "GameListContextMenuManageModToolTip": "Открывает окно управления модами", "GameListContextMenuManageMod": "Управление модами", "ControllerSettingsStickRange": "Диапазон:", - "DialogStopEmulationTitle": "Ryujinx - Остановить эмуляцию", + "DialogStopEmulationTitle": "Ryujinx - Остановка эмуляции", "DialogStopEmulationMessage": "Вы уверены, что хотите остановить эмуляцию?", - "SettingsTabCpu": "ЦП", + "SettingsTabCpu": "Процессор", "SettingsTabAudio": "Аудио", "SettingsTabNetwork": "Сеть", "SettingsTabNetworkConnection": "Подключение к сети", - "SettingsTabCpuCache": "Кэш ЦП", - "SettingsTabCpuMemory": "Память ЦП", + "SettingsTabCpuCache": "Кэш процессора", + "SettingsTabCpuMemory": "Режим процессора", "DialogUpdaterFlatpakNotSupportedMessage": "Пожалуйста, обновите Ryujinx через FlatHub.", "UpdaterDisabledWarningTitle": "Средство обновления отключено", "ControllerSettingsRotate90": "Повернуть на 90° по часовой стрелке", "IconSize": "Размер обложек", - "IconSizeTooltip": "Изменить размер обложек", + "IconSizeTooltip": "Меняет размер обложек", "MenuBarOptionsShowConsole": "Показать консоль", "ShaderCachePurgeError": "Ошибка очистки кэша шейдеров в {0}: {1}", "UserErrorNoKeys": "Ключи не найдены", @@ -578,7 +684,7 @@ "SettingsTabHotkeys": "Горячие клавиши", "SettingsTabHotkeysHotkeys": "Горячие клавиши", "SettingsTabHotkeysToggleVsyncHotkey": "Вертикальная синхронизация:", - "SettingsTabHotkeysScreenshotHotkey": "Скриншот:", + "SettingsTabHotkeysScreenshotHotkey": "Сделать скриншот:", "SettingsTabHotkeysShowUiHotkey": "Показать интерфейс:", "SettingsTabHotkeysPauseHotkey": "Пауза эмуляции:", "SettingsTabHotkeysToggleMuteHotkey": "Выключить звук:", @@ -597,6 +703,7 @@ "UserProfileWindowTitle": "Менеджер учетных записей", "CheatWindowTitle": "Менеджер читов", "DlcWindowTitle": "Управление DLC для {0} ({1})", + "ModWindowTitle": "Управление модами для {0} ({1})", "UpdateWindowTitle": "Менеджер обновлений игр", "CheatWindowHeading": "Доступные читы для {0} [{1}]", "BuildId": "ID версии:", @@ -607,33 +714,33 @@ "Save": "Сохранить", "Discard": "Отменить", "Paused": "Приостановлено", - "UserProfilesSetProfileImage": "Установить аватар профиля", - "UserProfileEmptyNameError": "Имя обязательно", + "UserProfilesSetProfileImage": "Установить аватар", + "UserProfileEmptyNameError": "Необходимо ввести никнейм", "UserProfileNoImageError": "Необходимо установить аватар", "GameUpdateWindowHeading": "Доступные обновления для {0} ({1})", "SettingsTabHotkeysResScaleUpHotkey": "Увеличить разрешение:", "SettingsTabHotkeysResScaleDownHotkey": "Уменьшить разрешение:", - "UserProfilesName": "Имя:", + "UserProfilesName": "Никнейм:", "UserProfilesUserId": "ID пользователя:", "SettingsTabGraphicsBackend": "Графический бэкенд", - "SettingsTabGraphicsBackendTooltip": "Выберите бэкенд, который будет использован в эмуляторе.\n\nVulkan является лучшим выбором для всех современных графических карт с актуальными драйверами. В Vulkan также включена более быстрая компиляция шейдеров (меньше статтеров) для всех GPU.\n\nOpenGL может достичь лучших результатов на старых GPU Nvidia, на старых GPU AMD на Linux или на GPU с небольшим количеством VRAM, хотя статтеров при компиляции шейдеров будет больше.\n\nРекомендуется использовать Vulkan. Используйте OpenGL, если ваш GPU не поддерживает Vulkan даже с актуальными драйверами.", - "SettingsEnableTextureRecompression": "Включить пережатие текстур", - "SettingsEnableTextureRecompressionTooltip": "Сжатие ASTC текстур для уменьшения использования VRAM. \n\nИгры, использующие этот формат текстур: Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder и The Legend of Zelda: Tears of the Kingdom. \n\nНа GPU с 4GiB VRAM или менее возможны вылеты при запуске этих игр. \n\nВключите, только если у вас заканчивается VRAM в вышеупомянутых играх. Рекомендуется оставить выключенным.", - "SettingsTabGraphicsPreferredGpu": "Предпочтительный GPU", - "SettingsTabGraphicsPreferredGpuTooltip": "Выберите GPU, который будет использоваться с графическим бэкендом Vulkan.\n\nЭта настройка не влияет на GPU, который будет использовать OpenGL.\n\nЕсли вы не уверены что нужно выбрать, используйте графический процессор, помеченный как \"dGPU\". Если его нет, оставьте выбор по умолчанию.", + "SettingsTabGraphicsBackendTooltip": "Выберает бэкенд, который будет использован в эмуляторе.\n\nVulkan является лучшим выбором для всех современных графических карт с актуальными драйверами. В Vulkan также включена более быстрая компиляция шейдеров (меньше статтеров) для всех видеоадаптеров.\n\nПри использовании OpenGL можно достичь лучших результатов на старых видеоадаптерах Nvidia и AMD в Linux или на видеоадаптерах с небольшим количеством видеопамяти, хотя статтеров при компиляции шейдеров будет больше.\n\nРекомендуется использовать Vulkan. Используйте OpenGL, если ваш видеоадаптер не поддерживает Vulkan даже с актуальными драйверами.", + "SettingsEnableTextureRecompression": "Пережимать текстуры", + "SettingsEnableTextureRecompressionTooltip": "Сжатие ASTC текстур для уменьшения использования VRAM. \n\nИгры, использующие этот формат текстур: Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder и The Legend of Zelda: Tears of the Kingdom. \nНа видеоадаптерах с 4GiB видеопамяти или менее возможны вылеты при запуске этих игр. \n\nВключите, только если у вас заканчивается видеопамять в вышеупомянутых играх. \n\nРекомендуется оставить выключенным.", + "SettingsTabGraphicsPreferredGpu": "Предпочтительный видеоадаптер", + "SettingsTabGraphicsPreferredGpuTooltip": "Выберает видеоадаптер, который будет использоваться графическим бэкендом Vulkan.\n\nЭта настройка не влияет на видеоадаптер, который будет использоваться с OpenGL.\n\nЕсли вы не уверены что нужно выбрать, используйте графический процессор, помеченный как \"dGPU\". Если его нет, оставьте выбор по умолчанию.", "SettingsAppRequiredRestartMessage": "Требуется перезапуск Ryujinx", "SettingsGpuBackendRestartMessage": "Графический бэкенд или настройки графического процессора были изменены. Требуется перезапуск для вступления в силу изменений.", "SettingsGpuBackendRestartSubMessage": "Перезапустить сейчас?", - "RyujinxUpdaterMessage": "Вы хотите обновить Ryujinx до последней версии?", + "RyujinxUpdaterMessage": "Обновить Ryujinx до последней версии?", "SettingsTabHotkeysVolumeUpHotkey": "Увеличить громкость:", "SettingsTabHotkeysVolumeDownHotkey": "Уменьшить громкость:", - "SettingsEnableMacroHLE": "Включить Macro HLE", - "SettingsEnableMacroHLETooltip": "Высокоуровневая эмуляции макроса GPU.\n\nПовышает производительность, но может вызывать графические сбои в некоторых играх.\n\nРекомендуется оставить включенным.", - "SettingsEnableColorSpacePassthrough": "Пропуск цветового пространства", + "SettingsEnableMacroHLE": "Использовать макрос высокоуровневой эмуляции видеоадаптера", + "SettingsEnableMacroHLETooltip": "Высокоуровневая эмуляции макрокода видеоадаптера.\n\nПовышает производительность, но может вызывать графические артефакты в некоторых играх.\n\nРекомендуется оставить включенным.", + "SettingsEnableColorSpacePassthrough": "Пропускать цветовое пространство", "SettingsEnableColorSpacePassthroughTooltip": "Направляет бэкенд Vulkan на передачу информации о цвете без указания цветового пространства. Для пользователей с экранами с расширенной гаммой данная настройка приводит к получению более ярких цветов за счет снижения корректности цветопередачи.", "VolumeShort": "Громкость", "UserProfilesManageSaves": "Управление сохранениями", - "DeleteUserSave": "Вы хотите удалить сохранения для этой игры?", + "DeleteUserSave": "Удалить сохранения для этой игры?", "IrreversibleActionNote": "Данное действие является необратимым.", "SaveManagerHeading": "Редактирование сохранений для {0} ({1})", "SaveManagerTitle": "Менеджер сохранений", @@ -644,10 +751,10 @@ "Recover": "Восстановление", "UserProfilesRecoverHeading": "Были найдены сохранения для следующих аккаунтов", "UserProfilesRecoverEmptyList": "Нет учетных записей для восстановления", - "GraphicsAATooltip": "Применимое сглаживание для рендера.\n\nFXAA размывает большую часть изображения, SMAA попытается найти \"зазубренные\" края и сгладить их.\n\nНе рекомендуется использовать вместе с масштабирующим фильтром FSR.\n\nЭта опция может быть изменена во время игры по нажатию \"Применить\" ниже; Вы можете просто переместить окно настроек в сторону и поэкспериментировать, пока не найдёте подходящую настройку игры.\n\nРекомендуется использовать \"Нет\".", + "GraphicsAATooltip": "Применимое сглаживание для рендера.\n\nFXAA размывает большую часть изображения, SMAA попытается найти \"зазубренные\" края и сгладить их.\n\nНе рекомендуется использовать вместе с масштабирующим фильтром FSR.\n\nЭта опция может быть изменена во время игры по нажатию \"Применить\" ниже; \nВы можете просто переместить окно настроек в сторону и поэкспериментировать, пока не найдёте подходящую настройку игры.\n\nРекомендуется использовать \"Нет\".", "GraphicsAALabel": "Сглаживание:", "GraphicsScalingFilterLabel": "Интерполяция:", - "GraphicsScalingFilterTooltip": "Выберите фильтрацию, которая будет применяться при масштабировании.\n\n\"Билинейная\" хорошо работает для 3D-игр и является настройкой по умолчанию.\n\n\"Ступенчатая\" рекомендуется для пиксельных игр.\n\n\"FSR\" это фильтр резкости, который не рекомендуется использовать с FXAA или SMAA.\n\nЭта опция может быть изменена во время игры по нажатию кнопки \"Применить\" ниже; Вы можете просто переместить окно настроек в сторону и поэкспериментировать, пока не подберете подходящие настройки для конкретной игры.\n\nРекомендуется использовать \"Билинейная\".", + "GraphicsScalingFilterTooltip": "Фильтрация текстур, которая будет применяться при масштабировании.\n\nБилинейная хорошо работает для 3D-игр и является настройкой по умолчанию.\n\nСтупенчатая рекомендуется для пиксельных игр.\n\nFSR это фильтр резкости, который не рекомендуется использовать с FXAA или SMAA.\n\nЭта опция может быть изменена во время игры по нажатию кнопки \"Применить\" ниже; \nВы можете просто переместить окно настроек в сторону и поэкспериментировать, пока не подберете подходящие настройки для конкретной игры.\n\nРекомендуется использовать \"Билинейная\".", "GraphicsScalingFilterBilinear": "Билинейная", "GraphicsScalingFilterNearest": "Ступенчатая", "GraphicsScalingFilterFsr": "FSR", @@ -667,7 +774,7 @@ "AboutChangelogButtonTooltipMessage": "Нажмите, чтобы открыть список изменений для этой версии", "SettingsTabNetworkMultiplayer": "Мультиплеер", "MultiplayerMode": "Режим:", - "MultiplayerModeTooltip": "Изменение многопользовательского режима LDN.\n\nLdnMitm модифицирует функциональность локальной беспроводной/игры на одном устройстве в играх, позволяя играть с другими пользователями Ryujinx или взломанными консолями Nintendo Switch с установленным модулем ldn_mitm, находящимися в одной локальной сети друг с другом.\n\nМногопользовательская игра требует наличия у всех игроков одной и той же версии игры (т.е. Super Smash Bros. Ultimate v13.0.1 не может подключиться к v13.0.0).\n\nРекомендуется оставить отключенным.", + "MultiplayerModeTooltip": "Меняет многопользовательский режим LDN.\n\nLdnMitm модифицирует функциональность локальной беспроводной/игры на одном устройстве в играх, позволяя играть с другими пользователями Ryujinx или взломанными консолями Nintendo Switch с установленным модулем ldn_mitm, находящимися в одной локальной сети друг с другом.\n\nМногопользовательская игра требует наличия у всех игроков одной и той же версии игры (т.е. Super Smash Bros. Ultimate v13.0.1 не может подключиться к v13.0.0).\n\nРекомендуется оставить отключенным.", "MultiplayerModeDisabled": "Отключено", "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/th_TH.json b/src/Ryujinx/Assets/Locales/th_TH.json index 23745ad94c..6294422695 100644 --- a/src/Ryujinx/Assets/Locales/th_TH.json +++ b/src/Ryujinx/Assets/Locales/th_TH.json @@ -11,27 +11,31 @@ "MenuBarFile": "ไฟล์", "MenuBarFileOpenFromFile": "โหลดแอปพลิเคชั่นจากไฟล์", "MenuBarFileOpenUnpacked": "โหลดเกมที่คลายแพ็กแล้ว", - "MenuBarFileOpenEmuFolder": "เปิดโฟลเดอร์ รียูจินซ์", + "MenuBarFileOpenEmuFolder": "เปิดโฟลเดอร์ Ryujinx", "MenuBarFileOpenLogsFolder": "เปิดโฟลเดอร์ Logs", - "MenuBarFileExit": "ออก", + "MenuBarFileExit": "_ออก", "MenuBarOptions": "_ตัวเลือก", "MenuBarOptionsToggleFullscreen": "สลับการแสดงผลแบบเต็มหน้าจอ", "MenuBarOptionsStartGamesInFullscreen": "เริ่มเกมในโหมดเต็มหน้าจอ", "MenuBarOptionsStopEmulation": "หยุดการจำลอง", - "MenuBarOptionsSettings": "การตั้งค่า", - "MenuBarOptionsManageUserProfiles": "จัดการโปรไฟล์ผู้ใช้งาน", + "MenuBarOptionsSettings": "_ตั้งค่า", + "MenuBarOptionsManageUserProfiles": "_จัดการโปรไฟล์ผู้ใช้งาน", "MenuBarActions": "การดำเนินการ", "MenuBarOptionsSimulateWakeUpMessage": "จำลองข้อความปลุก", - "MenuBarActionsScanAmiibo": "สแกนหา อะมิโบ", + "MenuBarActionsScanAmiibo": "สแกนหา Amiibo", "MenuBarTools": "_เครื่องมือ", "MenuBarToolsInstallFirmware": "ติดตั้งเฟิร์มแวร์", "MenuBarFileToolsInstallFirmwareFromFile": "ติดตั้งเฟิร์มแวร์จาก ไฟล์ XCI หรือ ไฟล์ ZIP", "MenuBarFileToolsInstallFirmwareFromDirectory": "ติดตั้งเฟิร์มแวร์จากไดเร็กทอรี", "MenuBarToolsManageFileTypes": "จัดการประเภทไฟล์", - "MenuBarToolsInstallFileTypes": "ติดตั้งประเภทไฟล์", - "MenuBarToolsUninstallFileTypes": "ถอนการติดตั้งประเภทไฟล์", + "MenuBarToolsInstallFileTypes": "ติดตั้งตามประเภทของไฟล์", + "MenuBarToolsUninstallFileTypes": "ถอนการติดตั้งตามประเภทของไฟล์", + "MenuBarView": "_View", + "MenuBarViewWindow": "Window Size", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", "MenuBarHelp": "_ช่วยเหลือ", - "MenuBarHelpCheckForUpdates": "ตรวจหาการอัพเดต", + "MenuBarHelpCheckForUpdates": "ตรวจสอบอัปเดต", "MenuBarHelpAbout": "เกี่ยวกับ", "MenuSearch": "กำลังค้นหา...", "GameListHeaderFavorite": "ชื่นชอบ", @@ -39,31 +43,31 @@ "GameListHeaderApplication": "ชื่อ", "GameListHeaderDeveloper": "ผู้พัฒนา", "GameListHeaderVersion": "เวอร์ชั่น", - "GameListHeaderTimePlayed": "เวลาที่เล่นไปแล้ว", + "GameListHeaderTimePlayed": "เล่นไปแล้ว", "GameListHeaderLastPlayed": "เล่นล่าสุด", "GameListHeaderFileExtension": "นามสกุลไฟล์", "GameListHeaderFileSize": "ขนาดไฟล์", - "GameListHeaderPath": "ที่เก็บไฟล์", + "GameListHeaderPath": "ที่อยู่ไฟล์", "GameListContextMenuOpenUserSaveDirectory": "เปิดไดเร็กทอรี่บันทึกของผู้ใช้", "GameListContextMenuOpenUserSaveDirectoryToolTip": "เปิดไดเร็กทอรี่ซึ่งมีการบันทึกผู้ใช้ของแอปพลิเคชัน", - "GameListContextMenuOpenDeviceSaveDirectory": "เปิดไดเร็กทอรีบันทึกของอุปกรณ์", + "GameListContextMenuOpenDeviceSaveDirectory": "เปิดไดเร็กทอรี่บันทึกของอุปกรณ์", "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "เปิดไดเรกทอรี่ซึ่งมีบันทึกอุปกรณ์ของแอปพลิเคชัน", - "GameListContextMenuOpenBcatSaveDirectory": "เปิดไดเรกทอรีบันทึก BCAT", + "GameListContextMenuOpenBcatSaveDirectory": "เปิดไดเรกทอรี่บันทึก BCAT", "GameListContextMenuOpenBcatSaveDirectoryToolTip": "เปิดไดเรกทอรี่ซึ่งมีการบันทึก BCAT ของแอปพลิเคชัน", - "GameListContextMenuManageTitleUpdates": "จัดการ การอัปเดตหัวข้อ", + "GameListContextMenuManageTitleUpdates": "จัดการอัปเดตตามหัวข้อ", "GameListContextMenuManageTitleUpdatesToolTip": "เปิดหน้าต่างการจัดการการอัพเดตหัวข้อ", "GameListContextMenuManageDlc": "จัดการ DLC", - "GameListContextMenuManageDlcToolTip": "เปิดหน้าต่างการจัดการ DLC", - "GameListContextMenuCacheManagement": "การบริหารจัดการแคช", + "GameListContextMenuManageDlcToolTip": "เปิดหน้าต่างจัดการ DLC", + "GameListContextMenuCacheManagement": "จัดการ แคช", "GameListContextMenuCacheManagementPurgePptc": "เพิ่มเข้าคิวงาน PPTC ที่สร้างใหม่", "GameListContextMenuCacheManagementPurgePptcToolTip": "ทริกเกอร์ PPTC ให้สร้างใหม่ในเวลาบูตเมื่อเปิดตัวเกมครั้งถัดไป", - "GameListContextMenuCacheManagementPurgeShaderCache": "ล้าง เชเดอร์แคช", - "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "ลบ เชเดอร์แคช ของแอปพลิเคชัน", - "GameListContextMenuCacheManagementOpenPptcDirectory": "เปิดไดเรกทอรี่่ PPTC", - "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "เปิดไดเร็กทอรีที่มี PPTC แคช ของแอปพลิเคชัน", - "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "เปิดไดเรกทอรี่ เชเดอร์แคช", - "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "เปิดไดเรกทอรี่ที่มี แคชเชเดอร์ ของแอปพลิเคชัน", - "GameListContextMenuExtractData": "แยกข้อมูล", + "GameListContextMenuCacheManagementPurgeShaderCache": "ล้างแคช พื้นผิวและแสงเงา", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "ลบแคช พื้นผิวและแสงเงา ของแอปพลิเคชัน", + "GameListContextMenuCacheManagementOpenPptcDirectory": "เปิดไดเรกทอรี่ PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "เปิดไดเร็กทอรี่ PPTC แคช ของแอปพลิเคชัน", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "เปิดไดเรกทอรี่ แคช พื้นผิวและแสงเงา", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "เปิดไดเรกทอรี่ แคช พื้นผิวและแสงเงา ของแอปพลิเคชัน", + "GameListContextMenuExtractData": "แยกส่วนข้อมูล", "GameListContextMenuExtractDataExeFS": "ExeFS", "GameListContextMenuExtractDataExeFSToolTip": "แยกส่วน ExeFS ออกจากการกำหนดค่าปัจจุบันของแอปพลิเคชัน (รวมถึงการอัปเดต)", "GameListContextMenuExtractDataRomFS": "RomFS", @@ -73,28 +77,29 @@ "GameListContextMenuCreateShortcut": "สร้างทางลัดของแอปพลิเคชัน", "GameListContextMenuCreateShortcutToolTip": "สร้างทางลัดบนเดสก์ท็อปที่เรียกใช้แอปพลิเคชันที่เลือก", "GameListContextMenuCreateShortcutToolTipMacOS": "สร้างทางลัดในโฟลเดอร์ Applications ของ macOS ที่เรียกใช้ Application ที่เลือก", - "GameListContextMenuOpenModsDirectory": "เปิดไดเรกทอรี่ Mods", - "GameListContextMenuOpenModsDirectoryToolTip": "เปิดไดเร็กทอรีซึ่งมี Mods ของแอปพลิเคชัน", - "GameListContextMenuOpenSdModsDirectory": "เปิดไดเร็กทอรี Mods Atmosphere", - "GameListContextMenuOpenSdModsDirectoryToolTip": "เปิดไดเร็กทอรี Atmosphere ของการ์ด SD สำรองซึ่งมี Mods ของแอปพลิเคชัน มีประโยชน์สำหรับ mods ที่บรรจุมากับฮาร์ดแวร์จริง", + "GameListContextMenuOpenModsDirectory": "เปิดไดเร็กทอรี่ Mods", + "GameListContextMenuOpenModsDirectoryToolTip": "เปิดไดเร็กทอรี่ Mods ของแอปพลิเคชัน", + "GameListContextMenuOpenSdModsDirectory": "เปิดไดเร็กทอรี่ Mods Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "เปิดไดเร็กทอรี่ Atmosphere ของการ์ด SD สำรองซึ่งมี Mods ของแอปพลิเคชัน มีประโยชน์สำหรับ Mods ที่บรรจุมากับฮาร์ดแวร์จริง", "StatusBarGamesLoaded": "เกมส์โหลดแล้ว {0}/{1}", "StatusBarSystemVersion": "เวอร์ชั่นของระบบ: {0}", - "LinuxVmMaxMapCountDialogTitle": "ตรวจพบขีดจำกัดต่ำสุดสำหรับการแมปหน่วยความจำ", + "LinuxVmMaxMapCountDialogTitle": "ตรวจพบขีดจำกัดต่ำสุด สำหรับการแมปหน่วยความจำ", "LinuxVmMaxMapCountDialogTextPrimary": "คุณต้องการที่จะเพิ่มค่า vm.max_map_count ไปยัง {0}", "LinuxVmMaxMapCountDialogTextSecondary": "บางเกมอาจพยายามสร้างการแมปหน่วยความจำมากกว่าที่ได้รับอนุญาตในปัจจุบัน รียูจินซ์ จะปิดตัวลงเมื่อเกินขีดจำกัดนี้", "LinuxVmMaxMapCountDialogButtonUntilRestart": "ใช่, จนกว่าจะรีสตาร์ทครั้งถัดไป", "LinuxVmMaxMapCountDialogButtonPersistent": "ใช่, อย่างถาวร", "LinuxVmMaxMapCountWarningTextPrimary": "จำนวนสูงสุดของการแม็ปหน่วยความจำ ต่ำกว่าที่แนะนำ", "LinuxVmMaxMapCountWarningTextSecondary": "ค่าปัจจุบันของ vm.max_map_count ({0}) มีค่าต่ำกว่า {1} บางเกมอาจพยายามสร้างการแมปหน่วยความจำมากกว่าที่ได้รับอนุญาตในปัจจุบัน รียูจินซ์ จะปิดตัวลงเมื่อเกินขีดจำกัดนี้\n\nคุณอาจต้องการเพิ่มขีดจำกัดด้วยตนเองหรือติดตั้ง pkexec ซึ่งอนุญาตให้ ริวจินซ์ เพื่อช่วยเหลือคุณได้", - "Settings": "การตั้งค่า", + "Settings": "ตั้งค่า", "SettingsTabGeneral": "หน้าจอผู้ใช้", "SettingsTabGeneralGeneral": "ทั่วไป", "SettingsTabGeneralEnableDiscordRichPresence": "เปิดใช้งาน Discord Rich Presence", "SettingsTabGeneralCheckUpdatesOnLaunch": "ตรวจหาการอัปเดตเมื่อเปิดโปรแกรม", "SettingsTabGeneralShowConfirmExitDialog": "แสดง \"ยืนยันการออก\" กล่องข้อความโต้ตอบ", + "SettingsTabGeneralRememberWindowState": "Remember Window Size/Position", "SettingsTabGeneralHideCursor": "ซ่อน เคอร์เซอร์:", "SettingsTabGeneralHideCursorNever": "ไม่มี", - "SettingsTabGeneralHideCursorOnIdle": "ซ่อนอัตโนมัติ", + "SettingsTabGeneralHideCursorOnIdle": "เมื่อไม่ได้ใช้", "SettingsTabGeneralHideCursorAlways": "ตลอดเวลา", "SettingsTabGeneralGameDirectories": "ไดเรกทอรี่ของเกม", "SettingsTabGeneralAdd": "เพิ่ม", @@ -127,13 +132,13 @@ "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "สเปน (ลาตินอเมริกา)", "SettingsTabSystemSystemLanguageSimplifiedChinese": "จีน (ตัวย่อ)", "SettingsTabSystemSystemLanguageTraditionalChinese": "จีน (ดั้งเดิม)", - "SettingsTabSystemSystemTimeZone": "โซนเวลาของระบบ:", + "SettingsTabSystemSystemTimeZone": "เขตเวลาของระบบ:", "SettingsTabSystemSystemTime": "เวลาของระบบ:", "SettingsTabSystemEnableVsync": "VSync", - "SettingsTabSystemEnablePptc": "PPTC (แคชการแปลแบบถาวรที่มีโปรไฟล์)", - "SettingsTabSystemEnableFsIntegrityChecks": "การตรวจสอบความถูกต้องของ FS", - "SettingsTabSystemAudioBackend": "แบ็กเอนด์เสียง:", - "SettingsTabSystemAudioBackendDummy": "ดัมมี่", + "SettingsTabSystemEnablePptc": "PPTC (แคชโปรไฟล์การแปลแบบถาวร)", + "SettingsTabSystemEnableFsIntegrityChecks": "ตรวจสอบความถูกต้องของ FS", + "SettingsTabSystemAudioBackend": "ระบบเสียงเบื้องหลัง:", + "SettingsTabSystemAudioBackendDummy": "Dummy", "SettingsTabSystemAudioBackendOpenAL": "OpenAL", "SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSDL2": "SDL2", @@ -142,9 +147,9 @@ "SettingsTabSystemExpandDramSize": "ใช้รูปแบบหน่วยความจำสำรอง (โหมดนักพัฒนา)", "SettingsTabSystemIgnoreMissingServices": "ไม่สนใจบริการที่ขาดหายไป", "SettingsTabGraphics": "กราฟิก", - "SettingsTabGraphicsAPI": "เอพีไอของกราฟิก", - "SettingsTabGraphicsEnableShaderCache": "เปิดใช้งาน เชเดอร์ แคช", - "SettingsTabGraphicsAnisotropicFiltering": "ตัวกรองแบบ แอนไอโซทรอปิก:", + "SettingsTabGraphicsAPI": "กราฟฟิก API", + "SettingsTabGraphicsEnableShaderCache": "เปิดใช้งาน แคชพื้นผิวและแสงเงา", + "SettingsTabGraphicsAnisotropicFiltering": "ตัวกรองแบบ Anisotropic:", "SettingsTabGraphicsAnisotropicFilteringAuto": "อัตโนมัติ", "SettingsTabGraphicsAnisotropicFiltering2x": "2x", "SettingsTabGraphicsAnisotropicFiltering4x": "4x", @@ -164,26 +169,26 @@ "SettingsTabGraphicsAspectRatio32x9": "32:9", "SettingsTabGraphicsAspectRatioStretch": "ยืดภาพเพื่อให้พอดีกับหน้าต่าง", "SettingsTabGraphicsDeveloperOptions": "ตัวเลือกนักพัฒนา", - "SettingsTabGraphicsShaderDumpPath": "ที่เก็บไฟล์ดัมพ์ของ เชเดอร์กราฟิก:", - "SettingsTabLogging": "การบันทึก", - "SettingsTabLoggingLogging": "การบันทึก", - "SettingsTabLoggingEnableLoggingToFile": "เปิดใช้งาน การบันทึกไปยังไฟล์", - "SettingsTabLoggingEnableStubLogs": "เปิดใช้งาน บันทึกของต้นขั้ว", - "SettingsTabLoggingEnableInfoLogs": "เปิดใช้งาน บันทึกของข้อมูล", - "SettingsTabLoggingEnableWarningLogs": "เปิดใช้งาน บันทึกคำเตือน", - "SettingsTabLoggingEnableErrorLogs": "เปิดใช้งาน บันทึกข้อผิดพลาด", - "SettingsTabLoggingEnableTraceLogs": "เปิดใช้งาน บันทึกการติดตาม", + "SettingsTabGraphicsShaderDumpPath": "ที่เก็บ ดัมพ์ไฟล์ พื้นผิวและแสงเงา:", + "SettingsTabLogging": "ประวัติ", + "SettingsTabLoggingLogging": "ประวัติ", + "SettingsTabLoggingEnableLoggingToFile": "เปิดใช้งาน ประวัติ ไปยังไฟล์", + "SettingsTabLoggingEnableStubLogs": "เปิดใช้งาน ประวัติ", + "SettingsTabLoggingEnableInfoLogs": "เปิดใช้งาน ประวัติการใช้งาน", + "SettingsTabLoggingEnableWarningLogs": "เปิดใช้งาน ประวัติคำเตือน", + "SettingsTabLoggingEnableErrorLogs": "เปิดใช้งาน ประวัติข้อผิดพลาด", + "SettingsTabLoggingEnableTraceLogs": "เปิดใช้งาน ประวัติการติดตาม", "SettingsTabLoggingEnableGuestLogs": "เปิดใช้งาน บันทึกของผู้เยี่ยมชม", - "SettingsTabLoggingEnableFsAccessLogs": "เปิดใช้งาน บันทึกการเข้าถึง Fs", - "SettingsTabLoggingFsGlobalAccessLogMode": "โหมดบันทึกการเข้าถึงส่วนกลาง:", + "SettingsTabLoggingEnableFsAccessLogs": "เปิดใช้งาน ประวัติการเข้าถึง Fs", + "SettingsTabLoggingFsGlobalAccessLogMode": "โหมด ประวัติการเข้าถึงส่วนกลาง:", "SettingsTabLoggingDeveloperOptions": "ตัวเลือกนักพัฒนา", "SettingsTabLoggingDeveloperOptionsNote": "คำเตือน: จะทำให้ประสิทธิภาพลดลง", - "SettingsTabLoggingGraphicsBackendLogLevel": "ระดับการบันทึก แบ็กเอนด์กราฟิก:", + "SettingsTabLoggingGraphicsBackendLogLevel": "ระดับการบันทึกประวัติ กราฟิกเบื้องหลัง:", "SettingsTabLoggingGraphicsBackendLogLevelNone": "ไม่มี", "SettingsTabLoggingGraphicsBackendLogLevelError": "ผิดพลาด", - "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "ชะลอตัว", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "ช้าลง", "SettingsTabLoggingGraphicsBackendLogLevelAll": "ทั้งหมด", - "SettingsTabLoggingEnableDebugLogs": "เปิดใช้งานบันทึกการแก้ไขข้อบกพร่อง", + "SettingsTabLoggingEnableDebugLogs": "เปิดใช้งาน ประวัติแก้ไขข้อบกพร่อง", "SettingsTabInput": "ป้อนข้อมูล", "SettingsTabInputEnableDockedMode": "ด็อกโหมด", "SettingsTabInputDirectKeyboardAccess": "เข้าถึงคีย์บอร์ดโดยตรง", @@ -261,11 +266,112 @@ "ControllerSettingsMotionControllerSlot": "ช่องเสียบ คอนโทรลเลอร์:", "ControllerSettingsMotionMirrorInput": "นำเข้าการสะท้อน การควบคุม", "ControllerSettingsMotionRightJoyConSlot": "ช่องเสียบ จอยคอน ด้านขวา:", - "ControllerSettingsMotionServerHost": "เซิร์ฟเวอร์โฮสต์:", + "ControllerSettingsMotionServerHost": "เจ้าของเซิร์ฟเวอร์:", "ControllerSettingsMotionGyroSensitivity": "ความไวของไจโร:", - "ControllerSettingsMotionGyroDeadzone": "โซนที่ไม่ทำงานของไจโร:", + "ControllerSettingsMotionGyroDeadzone": "ส่วนไม่ทำงานของไจโร:", "ControllerSettingsSave": "บันทึก", "ControllerSettingsClose": "ปิด", + "KeyUnknown": "Unknown", + "KeyShiftLeft": "Shift Left", + "KeyShiftRight": "Shift Right", + "KeyControlLeft": "Ctrl Left", + "KeyMacControlLeft": "⌃ Left", + "KeyControlRight": "Ctrl Right", + "KeyMacControlRight": "⌃ Right", + "KeyAltLeft": "Alt Left", + "KeyMacAltLeft": "⌥ Left", + "KeyAltRight": "Alt Right", + "KeyMacAltRight": "⌥ Right", + "KeyWinLeft": "⊞ Left", + "KeyMacWinLeft": "⌘ Left", + "KeyWinRight": "⊞ Right", + "KeyMacWinRight": "⌘ Right", + "KeyMenu": "Menu", + "KeyUp": "Up", + "KeyDown": "Down", + "KeyLeft": "Left", + "KeyRight": "Right", + "KeyEnter": "Enter", + "KeyEscape": "Escape", + "KeySpace": "Space", + "KeyTab": "Tab", + "KeyBackSpace": "Backspace", + "KeyInsert": "Insert", + "KeyDelete": "Delete", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "Print Screen", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "Clear", + "KeyKeypad0": "Keypad 0", + "KeyKeypad1": "Keypad 1", + "KeyKeypad2": "Keypad 2", + "KeyKeypad3": "Keypad 3", + "KeyKeypad4": "Keypad 4", + "KeyKeypad5": "Keypad 5", + "KeyKeypad6": "Keypad 6", + "KeyKeypad7": "Keypad 7", + "KeyKeypad8": "Keypad 8", + "KeyKeypad9": "Keypad 9", + "KeyKeypadDivide": "Keypad Divide", + "KeyKeypadMultiply": "Keypad Multiply", + "KeyKeypadSubtract": "Keypad Subtract", + "KeyKeypadAdd": "Keypad Add", + "KeyKeypadDecimal": "Keypad Decimal", + "KeyKeypadEnter": "Keypad Enter", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "Unbound", + "GamepadLeftStick": "L Stick Button", + "GamepadRightStick": "R Stick Button", + "GamepadLeftShoulder": "Left Shoulder", + "GamepadRightShoulder": "Right Shoulder", + "GamepadLeftTrigger": "Left Trigger", + "GamepadRightTrigger": "Right Trigger", + "GamepadDpadUp": "Up", + "GamepadDpadDown": "Down", + "GamepadDpadLeft": "Left", + "GamepadDpadRight": "Right", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "Guide", + "GamepadMisc1": "Misc", + "GamepadPaddle1": "Paddle 1", + "GamepadPaddle2": "Paddle 2", + "GamepadPaddle3": "Paddle 3", + "GamepadPaddle4": "Paddle 4", + "GamepadTouchpad": "Touchpad", + "GamepadSingleLeftTrigger0": "Left Trigger 0", + "GamepadSingleRightTrigger0": "Right Trigger 0", + "GamepadSingleLeftTrigger1": "Left Trigger 1", + "GamepadSingleRightTrigger1": "Right Trigger 1", + "StickLeft": "Left Stick", + "StickRight": "Right Stick", "UserProfilesSelectedUserProfile": "โปรไฟล์ผู้ใช้งานที่เลือก:", "UserProfilesSaveProfileName": "บันทึกชื่อโปรไฟล์", "UserProfilesChangeProfileImage": "เปลี่ยนรูปโปรไฟล์", @@ -273,25 +379,25 @@ "UserProfilesAddNewProfile": "สร้างโปรไฟล์ใหม่", "UserProfilesDelete": "ลบ", "UserProfilesClose": "ปิด", - "ProfileNameSelectionWatermark": "เลือกชื่อเล่น", - "ProfileImageSelectionTitle": "เลือกรูปโปรไฟล์ของคุณ", - "ProfileImageSelectionHeader": "เลือกรูปโปรไฟล์", - "ProfileImageSelectionNote": "คุณสามารถนำเข้ารูปโปรไฟล์ที่กำหนดเอง หรือเลือกอวาต้าจากเฟิร์มแวร์ระบบได้", - "ProfileImageSelectionImportImage": "นำเข้าไฟล์รูปภาพ", - "ProfileImageSelectionSelectAvatar": "เลือกรูปอวาต้าเฟิร์มแวร์", + "ProfileNameSelectionWatermark": "เลือก ชื่อเล่น", + "ProfileImageSelectionTitle": "เลือก รูปโปรไฟล์ ของคุณ", + "ProfileImageSelectionHeader": "เลือก รูปโปรไฟล์", + "ProfileImageSelectionNote": "คุณสามารถนำเข้ารูปโปรไฟล์ที่กำหนดเอง หรือ เลือกอวาต้าจากเฟิร์มแวร์ระบบได้", + "ProfileImageSelectionImportImage": "นำเข้า ไฟล์รูปภาพ", + "ProfileImageSelectionSelectAvatar": "เลือก รูปอวาต้า เฟิร์มแวร์", "InputDialogTitle": "กล่องโต้ตอบการป้อนข้อมูล", "InputDialogOk": "ตกลง", "InputDialogCancel": "ยกเลิก", - "InputDialogAddNewProfileTitle": "เลือกชื่อโปรไฟล์", + "InputDialogAddNewProfileTitle": "เลือก ชื่อโปรไฟล์", "InputDialogAddNewProfileHeader": "กรุณาใส่ชื่อโปรไฟล์", "InputDialogAddNewProfileSubtext": "(ความยาวสูงสุด: {0})", - "AvatarChoose": "เลือกรูปอวาต้าของคุณ", + "AvatarChoose": "เลือก รูปอวาต้า ของคุณ", "AvatarSetBackgroundColor": "ตั้งค่าสีพื้นหลัง", "AvatarClose": "ปิด", - "ControllerSettingsLoadProfileToolTip": "โหลดโปรไฟล์", - "ControllerSettingsAddProfileToolTip": "เพิ่มโปรไฟล์", - "ControllerSettingsRemoveProfileToolTip": "ลบโปรไฟล์", - "ControllerSettingsSaveProfileToolTip": "บันทึกโปรไฟล์", + "ControllerSettingsLoadProfileToolTip": "โหลด โปรไฟล์", + "ControllerSettingsAddProfileToolTip": "เพิ่ม โปรไฟล์", + "ControllerSettingsRemoveProfileToolTip": "ลบ โปรไฟล์", + "ControllerSettingsSaveProfileToolTip": "บันทึก โปรไฟล์", "MenuBarFileToolsTakeScreenshot": "ถ่ายภาพหน้าจอ", "MenuBarFileToolsHideUi": "ซ่อน UI", "GameListContextMenuRunApplication": "เรียกใช้แอปพลิเคชัน", @@ -343,11 +449,11 @@ "DialogFirmwareInstallEmbeddedMessage": "คุณต้องการติดตั้งเฟิร์มแวร์ที่ฝังอยู่ในเกมนี้หรือไม่? (เฟิร์มแวร์ {0})", "DialogFirmwareInstallEmbeddedSuccessMessage": "ไม่พบเฟิร์มแวร์ที่ติดตั้งไว้ แต่ รียูจินซ์ สามารถติดตั้งเฟิร์มแวร์ได้ {0} จากเกมที่ให้มา\nตอนนี้โปรแกรมจำลองจะเริ่มทำงาน", "DialogFirmwareNoFirmwareInstalledMessage": "ไม่มีการติดตั้งเฟิร์มแวร์", - "DialogFirmwareInstalledMessage": "เฟิร์มแวร์ทำการติดตั้งแล้ว {0}", - "DialogInstallFileTypesSuccessMessage": "ติดตั้งประเภทไฟล์สำเร็จแล้ว!", - "DialogInstallFileTypesErrorMessage": "ติดตั้งประเภทไฟล์ไม่สำเร็จ", - "DialogUninstallFileTypesSuccessMessage": "ถอนการติดตั้งประเภทไฟล์สำเร็จแล้ว!", - "DialogUninstallFileTypesErrorMessage": "ไม่สามารถถอนการติดตั้งประเภทไฟล์ได้", + "DialogFirmwareInstalledMessage": "เฟิร์มแวร์ติดตั้งแล้ว {0}", + "DialogInstallFileTypesSuccessMessage": "ติดตั้งตามประเภทของไฟล์สำเร็จแล้ว!", + "DialogInstallFileTypesErrorMessage": "ติดตั้งตามประเภทของไฟล์ไม่สำเร็จ", + "DialogUninstallFileTypesSuccessMessage": "ถอนการติดตั้งตามประเภทของไฟล์สำเร็จแล้ว!", + "DialogUninstallFileTypesErrorMessage": "ไม่สามารถถอนการติดตั้งตามประเภทของไฟล์ได้", "DialogOpenSettingsWindowLabel": "เปิดหน้าต่างการตั้งค่า", "DialogControllerAppletTitle": "แอพเพล็ตคอนโทรลเลอร์", "DialogMessageDialogErrorExceptionMessage": "เกิดข้อผิดพลาดในการแสดงกล่องโต้ตอบข้อความ: {0}", @@ -369,7 +475,7 @@ "DialogShaderDeletionMessage": "คุณกำลังจะลบ เชเดอร์แคช:\n\n{0}\n\nคุณแน่ใจหรือไม่ว่าต้องการดำเนินการต่อหรือไม่?", "DialogShaderDeletionErrorMessage": "เกิดข้อผิดพลาดในการล้าง เชเดอร์แคช {0}: {1}", "DialogRyujinxErrorMessage": "รียูจินซ์ พบข้อผิดพลาด", - "DialogInvalidTitleIdErrorMessage": "ข้อผิดพลาดของ ยูไอ: เกมที่เลือกไม่มีชื่อ ID ที่ถูกต้อง", + "DialogInvalidTitleIdErrorMessage": "ข้อผิดพลาดของ UI: เกมที่เลือกไม่มีชื่อ ID ที่ถูกต้อง", "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "ไม่พบเฟิร์มแวร์ของระบบที่ถูกต้อง {0}.", "DialogFirmwareInstallerFirmwareInstallTitle": "ติดตั้งเฟิร์มแวร์ {0}", "DialogFirmwareInstallerFirmwareInstallMessage": "นี่คื่อเวอร์ชั่นของระบบ {0} ที่ได้รับการติดตั้งเมื่อเร็วๆ นี้", @@ -401,7 +507,7 @@ "DialogModManagerDeletionWarningMessage": "คุณกำลังจะลบ ม็อด: {0}\n\nคุณแน่ใจหรือไม่ว่าต้องการดำเนินการต่อ?", "DialogModManagerDeletionAllWarningMessage": "คุณกำลังจะลบม็อดทั้งหมดสำหรับชื่อนี้\n\nคุณแน่ใจหรือไม่ว่าต้องการดำเนินการต่อ?", "SettingsTabGraphicsFeaturesOptions": "คุณสมบัติ", - "SettingsTabGraphicsBackendMultithreading": "มัลติเธรด แบ็กเอนด์กราฟิก:", + "SettingsTabGraphicsBackendMultithreading": "มัลติเธรด กราฟิกเบื้องหลัง:", "CommonAuto": "อัตโนมัติ", "CommonOff": "ปิดการใช้งาน", "CommonOn": "เปิดใช้งาน", @@ -421,7 +527,7 @@ "AboutRyujinxAboutContent": "รียูจินซ์ เป็นอีมูเลเตอร์สำหรับ Nintendo Switch™\nโปรดสนับสนุนเราบน เพทรีออน\nรับข่าวสารล่าสุดทั้งหมดบน ทวิตเตอร์ หรือ ดิสคอร์ด ของเรา\nนักพัฒนาที่สนใจจะมีส่วนร่วมสามารถดูข้อมูลเพิ่มเติมได้ที่ กิตฮับ หรือ ดิสคอร์ด ของเรา", "AboutRyujinxMaintainersTitle": "ได้รับการดูแลรักษาโดย:", "AboutRyujinxMaintainersContentTooltipMessage": "คลิกเพื่อเปิดหน้าผู้ร่วมให้ข้อมูลในเบราว์เซอร์เริ่มต้นของคุณ", - "AboutRyujinxSupprtersTitle": "สนับสนุนบน เพทรีออน โดย:", + "AboutRyujinxSupprtersTitle": "ลายนามผู้สนับสนุนบน เพทรีออน:", "AmiiboSeriesLabel": "อะมิโบซีรีส์", "AmiiboCharacterLabel": "ตัวละคร", "AmiiboScanButtonLabel": "สแกนเลย", @@ -451,7 +557,7 @@ "CustomThemeCheckTooltip": "ใช้ธีม Avalonia แบบกำหนดเองสำหรับ GUI เพื่อเปลี่ยนรูปลักษณ์ของเมนูโปรแกรมจำลอง", "CustomThemePathTooltip": "ไปยังที่เก็บไฟล์ธีม GUI แบบกำหนดเอง", "CustomThemeBrowseTooltip": "เรียกดูธีม GUI ที่กำหนดเอง", - "DockModeToggleTooltip": "ด็อกโหมดทำให้ระบบจำลองการทำงานเสมือน Nintendo ที่กำลังเชื่อมต่ออยู่ด็อก สิ่งนี้จะปรับปรุงความเสถียรภาพของกราฟิกในเกมส่วนใหญ่ ในทางกลับกัน การปิดใช้จะทำให้ระบบจำลองทำงานเหมือนกับ Nintendo Switch แบบพกพา ส่งผลให้คุณภาพกราฟิกลดลง\n\nกำหนดค่าส่วนควบคุมของผู้เล่น 1 หากวางแผนที่จะใช้ด็อกโหมด กำหนดค่าการควบคุมแบบ แฮนด์เฮลด์ หากวางแผนที่จะใช้โหมดแฮนด์เฮลด์\n\nเปิดทิ้งไว้หากคุณไม่แน่ใจ", + "DockModeToggleTooltip": "ด็อกโหมด ทำให้ระบบจำลองการทำงานเสมือน Nintendo ที่กำลังเชื่อมต่ออยู่ด็อก สิ่งนี้จะปรับปรุงความเสถียรภาพของกราฟิกในเกมส่วนใหญ่ ในทางกลับกัน การปิดใช้จะทำให้ระบบจำลองทำงานเหมือนกับ Nintendo Switch แบบพกพา ส่งผลให้คุณภาพกราฟิกลดลง\n\nกำหนดค่าส่วนควบคุมของผู้เล่น 1 หากวางแผนที่จะใช้ด็อกโหมด กำหนดค่าการควบคุมแบบ แฮนด์เฮลด์ หากวางแผนที่จะใช้โหมดแฮนด์เฮลด์\n\nเปิดทิ้งไว้หากคุณไม่แน่ใจ", "DirectKeyboardTooltip": "รองรับการเข้าถึงแป้นพิมพ์โดยตรง (HID) ให้เกมเข้าถึงคีย์บอร์ดของคุณเป็นอุปกรณ์ป้อนข้อความ\n\nใช้งานได้กับเกมที่รองรับการใช้งานคีย์บอร์ดบนฮาร์ดแวร์ของ Switch เท่านั้น\n\nหากคุณไม่แน่ใจปล่อยให้ปิดอย่างนั้น", "DirectMouseTooltip": "รองรับการเข้าถึงเมาส์โดยตรง (HID) ให้เกมเข้าถึงเมาส์ของคุณเป็นอุปกรณ์ชี้ตำแหน่ง\n\nใช้งานได้เฉพาะกับเกมที่รองรับการควบคุมเมาส์บนฮาร์ดแวร์ของ Switch เท่านั้น ซึ่งมีอยู่ไม่มากนัก\n\nเมื่อเปิดใช้งาน ฟังก์ชั่นหน้าจอสัมผัสอาจไม่ทำงาน\n\nหากคุณไม่แน่ใจปล่อยให้ปิดอย่างนั้น", "RegionTooltip": "เปลี่ยนภูมิภาคของระบบ", @@ -476,27 +582,27 @@ "ResolutionScaleEntryTooltip": "สเกลความละเอียดจุดทศนิยม เช่น 1.5 ไม่ใช่จำนวนเต็มของสเกล มีแนวโน้มที่จะก่อให้เกิดปัญหาหรือความผิดพลาดได้", "AnisotropyTooltip": "ระดับของการกรองแบบ Anisotropic ตั้งค่าเป็นอัตโนมัติเพื่อใช้ค่าที่เกมร้องขอ", "AspectRatioTooltip": "อัตราส่วนภาพที่ใช้กับหน้าต่างตัวแสดงภาพ\n\nเปลี่ยนสิ่งนี้หากคุณใช้ตัวดัดแปลงอัตราส่วนกว้างยาวสำหรับเกมของคุณ ไม่เช่นนั้นกราฟิกจะถูกยืดออก\n\nทิ้งไว้ที่ 16:9 หากไม่แน่ใจ", - "ShaderDumpPathTooltip": "ที่เก็บ ดัมพ์ไฟล์ของ เชเดอร์กราฟิก", - "FileLogTooltip": "บันทึกการบันทึกคอนโซลลงในไฟล์บันทึกบนดิสก์ จะไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", - "StubLogTooltip": "พิมพ์ข้อความบันทึกต้นขั้วในคอนโซล จะไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", + "ShaderDumpPathTooltip": "ที่เก็บ ดัมพ์ไฟล์ พื้นผิวและแสงเงา", + "FileLogTooltip": "บันทึก ประวัติคอนโซลลงในไฟล์บันทึกบนดิสก์ จะไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", + "StubLogTooltip": "พิมพ์ข้อความประวัติในคอนโซล จะไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", "InfoLogTooltip": "พิมพ์ข้อความบันทึกข้อมูลในคอนโซล จะไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", - "WarnLogTooltip": "พิมพ์ข้อความบันทึกแจ้งตือนในคอนโซล จะไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", + "WarnLogTooltip": "พิมพ์ข้อความประวัติแจ้งตือนในคอนโซล จะไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", "ErrorLogTooltip": "พิมพ์ข้อความบันทึกข้อผิดพลาดในคอนโซล จะไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", - "TraceLogTooltip": "พิมพ์ข้อความบันทึกการติดตามในคอนโซล ไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", - "GuestLogTooltip": "พิมพ์ข้อความบันทึกของผู้เยี่ยมชมในคอนโซล ไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", + "TraceLogTooltip": "พิมพ์ข้อความประวัติการติดตามในคอนโซล ไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", + "GuestLogTooltip": "พิมพ์ข้อความประวัติของผู้เยี่ยมชมในคอนโซล ไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", "FileAccessLogTooltip": "พิมพ์ข้อความบันทึกการเข้าถึงไฟล์ในคอนโซล", - "FSAccessLogModeTooltip": "เปิดใช้งานเอาต์พุตบันทึกการเข้าถึง FS ไปยังคอนโซล โหมดที่เป็นไปได้คือ 0-3", + "FSAccessLogModeTooltip": "เปิดใช้งาน เอาต์พุตประวัติการเข้าถึง FS ไปยังคอนโซล โหมดที่เป็นไปได้คือ 0-3", "DeveloperOptionTooltip": "โปรดใช้ด้วยความระมัดระวัง", "OpenGlLogLevel": "จำเป็นต้องเปิดใช้งานระดับบันทึกที่เหมาะสม", - "DebugLogTooltip": "พิมพ์ข้อความบันทึกการแก้ไขข้อบกพร่องในคอนโซล\n\nใช้สิ่งนี้เฉพาะเมื่อได้รับคำแนะนำจากเจ้าหน้าที่โดยเฉพาะเท่านั้น เนื่องจากจะทำให้บันทึกอ่านยากและทำให้ประสิทธิภาพของโปรแกรมจำลองแย่ลง", + "DebugLogTooltip": "พิมพ์ข้อความประวัติการแก้ไขข้อบกพร่องในคอนโซล\n\nใช้สิ่งนี้เฉพาะเมื่อได้รับคำแนะนำจากเจ้าหน้าที่โดยเฉพาะเท่านั้น เนื่องจากจะทำให้บันทึกอ่านยากและทำให้ประสิทธิภาพของโปรแกรมจำลองแย่ลง", "LoadApplicationFileTooltip": "เปิด File Explorer เพื่อเลือกไฟล์ที่เข้ากันได้กับ Switch ที่จะโหลด", "LoadApplicationFolderTooltip": "เปิดตัวสำรวจไฟล์เพื่อเลือกไฟล์ที่เข้ากันได้กับ Switch ที่จะโหลด", - "OpenRyujinxFolderTooltip": "เปิดโฟลเดอร์ระบบไฟล์ รียูจินซ์", - "OpenRyujinxLogsTooltip": "เปิดโฟลเดอร์ที่มีการเขียนบันทึก", + "OpenRyujinxFolderTooltip": "เปิดโฟลเดอร์ระบบไฟล์ Ryujinx", + "OpenRyujinxLogsTooltip": "เปิดโฟลเดอร์ ที่เก็บไฟล์ประวัติ", "ExitTooltip": "ออกจากโปรแกรม รียูจินซ์", "OpenSettingsTooltip": "เปิดหน้าต่างการตั้งค่า", "OpenProfileManagerTooltip": "เปิดหน้าต่างตัวจัดการโปรไฟล์ผู้ใช้", - "StopEmulationTooltip": "หยุดการจำลองเกมปัจจุบันและกลับไปยังการเลือกเกม", + "StopEmulationTooltip": "หยุดการจำลองของเกมที่เปิดอยู่ในปัจจุบันและกลับไปยังการเลือกเกม", "CheckUpdatesTooltip": "ตรวจสอบการอัปเดตของ รียูจินซ์", "OpenAboutTooltip": "เปิดหน้าต่าง เกี่ยวกับ", "GridSize": "ขนาดตาราง", @@ -519,7 +625,7 @@ "SettingsTabNetwork": "เครือข่าย", "SettingsTabNetworkConnection": "การเชื่อมต่อเครือข่าย", "SettingsTabCpuCache": "ซีพียู แคช", - "SettingsTabCpuMemory": "ซีพียูเมมโมรี่ แคช", + "SettingsTabCpuMemory": "โหมดซีพียู", "DialogUpdaterFlatpakNotSupportedMessage": "โปรดอัปเดต รียูจินซ์ ผ่านช่องทาง FlatHub", "UpdaterDisabledWarningTitle": "ปิดใช้งานการอัปเดตแล้ว!", "ControllerSettingsRotate90": "หมุน 90 องศา ตามเข็มนาฬิกา", @@ -527,20 +633,20 @@ "IconSizeTooltip": "เปลี่ยนขนาดของไอคอนเกม", "MenuBarOptionsShowConsole": "แสดง คอนโซล", "ShaderCachePurgeError": "เกิดข้อผิดพลาดในการล้างแคชเชเดอร์ {0}: {1}", - "UserErrorNoKeys": "ไม่พบคีย์", - "UserErrorNoFirmware": "ไม่พบเฟิร์มแวร์", - "UserErrorFirmwareParsingFailed": "เกิดข้อผิดพลาดในการแยกวิเคราะห์เฟิร์มแวร์", - "UserErrorApplicationNotFound": "ไม่พบแอปพลิเคชัน", + "UserErrorNoKeys": "ไม่พบ คีย์", + "UserErrorNoFirmware": "ไม่พบ เฟิร์มแวร์", + "UserErrorFirmwareParsingFailed": "เกิดข้อผิดพลาดในการวิเคราะห์เฟิร์มแวร์", + "UserErrorApplicationNotFound": "ไม่พบ แอปพลิเคชัน", "UserErrorUnknown": "ข้อผิดพลาดที่ไม่รู้จัก", "UserErrorUndefined": "ข้อผิดพลาดที่ไม่ได้ระบุ", - "UserErrorNoKeysDescription": "รียูจินซ์ ไม่พบไฟล์ 'prod.keys' ของคุณ", - "UserErrorNoFirmwareDescription": "รียูจินซ์ ไม่พบเฟิร์มแวร์ที่ติดตั้งไว้", - "UserErrorFirmwareParsingFailedDescription": "รียูจินซ์ ไม่สามารถแยกวิเคราะห์เฟิร์มแวร์ที่ให้มาได้ ซึ่งมักมีสาเหตุมาจากคีย์ที่ล้าสมัย", + "UserErrorNoKeysDescription": "รียูจินซ์ ไม่พบไฟล์ 'prod.keys' ในเครื่องของคุณ", + "UserErrorNoFirmwareDescription": "รียูจินซ์ ไม่พบ เฟิร์มแวร์ที่ติดตั้งไว้ในเครื่องของคุณ", + "UserErrorFirmwareParsingFailedDescription": "รียูจินซ์ ไม่สามารถวิเคราะห์เฟิร์มแวร์ที่ให้มาได้ ซึ่งมักมีสาเหตุมาจากคีย์ที่ล้าสมัย", "UserErrorApplicationNotFoundDescription": "รียูจินซ์ ไม่พบแอปพลิเคชันที่ถูกต้องในที่เก็บไฟล์ที่กำหนด", - "UserErrorUnknownDescription": "เกิดข้อผิดพลาดที่ไม่รู้จักเกิดขึ้น!", + "UserErrorUnknownDescription": "เกิดข้อผิดพลาดที่ไม่รู้จัก!", "UserErrorUndefinedDescription": "เกิดข้อผิดพลาดที่ไม่สามารถระบุได้! สิ่งนี้ไม่ควรเกิดขึ้น โปรดติดต่อผู้พัฒนา!", "OpenSetupGuideMessage": "เปิดคู่มือการตั้งค่า", - "NoUpdate": "ไม่มีการอัพเดต", + "NoUpdate": "ไม่มีการอัปเดต", "TitleUpdateVersionLabel": "เวอร์ชั่น {0}", "RyujinxInfo": "รียูจินซ์ – ข้อมูล", "RyujinxConfirm": "รียูจินซ์ - ยืนยัน", @@ -565,16 +671,16 @@ "Docked": "ด็อก", "Handheld": "แฮนด์เฮลด์", "ConnectionError": "การเชื่อมต่อล้มเหลว", - "AboutPageDeveloperListMore": "{0} และอื่น ๆ...", + "AboutPageDeveloperListMore": "{0} และอื่นๆ ...", "ApiError": "ข้อผิดพลาดของ API", "LoadingHeading": "กำลังโหลด {0}", "CompilingPPTC": "กำลังคอมไพล์ PTC", - "CompilingShaders": "กำลังคอมไพล์ เชเดอร์", + "CompilingShaders": "กำลังคอมไพล์ พื้นผิวและแสงเงา", "AllKeyboards": "คีย์บอร์ดทั้งหมด", "OpenFileDialogTitle": "เลือกไฟล์ที่สนับสนุนเพื่อเปิด", "OpenFolderDialogTitle": "เลือกโฟลเดอร์ที่มีเกมที่แตกไฟล์แล้ว", "AllSupportedFormats": "รูปแบบที่รองรับทั้งหมด", - "RyujinxUpdater": "อัพเดต รียูจินซ์", + "RyujinxUpdater": "อัปเดต รียูจินซ์", "SettingsTabHotkeys": "ปุ่มลัดของคีย์บอร์ด", "SettingsTabHotkeysHotkeys": "ปุ่มลัดของคีย์บอร์ด", "SettingsTabHotkeysToggleVsyncHotkey": "สลับเป็น VSync:", @@ -582,7 +688,7 @@ "SettingsTabHotkeysShowUiHotkey": "แสดง UI:", "SettingsTabHotkeysPauseHotkey": "หยุดชั่วคราว:", "SettingsTabHotkeysToggleMuteHotkey": "ปิดเสียง:", - "ControllerMotionTitle": "การตั้งค่าการควบคุมการเคลื่อนไหว", + "ControllerMotionTitle": "ตั้งค่าควบคุมการเคลื่อนไหว", "ControllerRumbleTitle": "ตั้งค่าการสั่นไหว", "SettingsSelectThemeFileDialogTitle": "เลือกไฟล์ธีม", "SettingsXamlThemeFile": "ไฟล์ธีมรูปแบบ XAML", @@ -593,11 +699,12 @@ "Writable": "สามารถเขียนได้", "SelectDlcDialogTitle": "เลือกไฟล์ DLC", "SelectUpdateDialogTitle": "เลือกไฟล์อัพเดต", - "SelectModDialogTitle": "เลือกไดเรกทอรี ม็อด", + "SelectModDialogTitle": "เลือกไดเรกทอรี Mods", "UserProfileWindowTitle": "จัดการโปรไฟล์ผู้ใช้", "CheatWindowTitle": "จัดการสูตรโกง", "DlcWindowTitle": "จัดการเนื้อหาที่ดาวน์โหลดได้สำหรับ {0} ({1})", - "UpdateWindowTitle": "จัดการการอัพเดตชื่อเรื่อง", + "ModWindowTitle": "Manage Mods for {0} ({1})", + "UpdateWindowTitle": "จัดการอัปเดตหัวข้อ", "CheatWindowHeading": "สูตรโกงมีให้สำหรับ {0} [{1}]", "BuildId": "รหัสบิวด์:", "DlcWindowHeading": "{0} เนื้อหาที่สามารถดาวน์โหลดได้", @@ -608,21 +715,21 @@ "Discard": "ละทิ้ง", "Paused": "หยุดชั่วคราว", "UserProfilesSetProfileImage": "ตั้งค่ารูปโปรไฟล์", - "UserProfileEmptyNameError": "จำเป็นต้องมีการระบุชื่อ", + "UserProfileEmptyNameError": "จำเป็นต้องระบุชื่อ", "UserProfileNoImageError": "จำเป็นต้องตั้งค่ารูปโปรไฟล์", "GameUpdateWindowHeading": "จัดการอัพเดตสำหรับ {0} ({1})", "SettingsTabHotkeysResScaleUpHotkey": "เพิ่มความละเอียด:", "SettingsTabHotkeysResScaleDownHotkey": "ลดความละเอียด:", "UserProfilesName": "ชื่อ:", "UserProfilesUserId": "รหัสผู้ใช้:", - "SettingsTabGraphicsBackend": "แบ็กเอนด์กราฟิก", - "SettingsTabGraphicsBackendTooltip": "เลือกแบ็กเอนด์กราฟิกที่จะใช้ในโปรแกรมจำลอง\n\nโดยรวมแล้ว Vulkan นั้นดีกว่าสำหรับกราฟิกการ์ดรุ่นใหม่ทั้งหมด ตราบใดที่ไดรเวอร์ยังอัพเดทอยู่เสมอ Vulkan ยังมีคุณสมบัติการคอมไพล์เชเดอร์ที่เร็วขึ้น (ลดอาการกระตุก) ของผู้จำหน่าย GPU ทุกรายอยู่แล้ว\n\nOpenGL อาจได้รับผลลัพธ์ที่ดีกว่าบน Nvidia GPU รุ่นเก่า, AMD GPU รุ่นเก่าบน Linux หรือบน GPU ที่มี VRAM ต่ำกว่า แม้ว่าการคอมไพล์เชเดอร์จะสะดุดมากขึ้นก็ตาม\n\nตั้งค่าเป็น Vulkan หากไม่แน่ใจ ตั้งค่าเป็น OpenGL หาก GPU ของคุณไม่รองรับ Vulkan แม้จะมีไดรเวอร์กราฟิกล่าสุดก็ตาม", - "SettingsEnableTextureRecompression": "เปิดใช้งานการบีบอัดพื้นผิวอีกครั้ง", + "SettingsTabGraphicsBackend": "กราฟิกเบื้องหลัง", + "SettingsTabGraphicsBackendTooltip": "เลือกกราฟิกเบื้องหลังที่จะใช้ในโปรแกรมจำลอง\n\nโดยรวมแล้ว Vulkan นั้นดีกว่าสำหรับกราฟิกการ์ดรุ่นใหม่ทั้งหมด ตราบใดที่ไดรเวอร์ยังอัพเดทอยู่เสมอ Vulkan ยังมีคุณสมบัติการคอมไพล์เชเดอร์ที่เร็วขึ้น (ลดอาการกระตุก) ของผู้จำหน่าย GPU ทุกราย\n\nOpenGL อาจได้รับผลลัพธ์ที่ดีกว่าบน Nvidia GPU รุ่นเก่า, AMD GPU รุ่นเก่าบน Linux หรือบน GPU ที่มี VRAM ต่ำกว่า แม้ว่าการคอมไพล์เชเดอร์ จะทำให้อาการกระตุกมากขึ้นก็ตาม\n\nตั้งค่าเป็น Vulkan หากไม่แน่ใจ ตั้งค่าเป็น OpenGL หาก GPU ของคุณไม่รองรับ Vulkan แม้จะมีไดรเวอร์กราฟิกล่าสุดก็ตาม", + "SettingsEnableTextureRecompression": "เปิดใช้งาน การบีบอัดพื้นผิวอีกครั้ง", "SettingsEnableTextureRecompressionTooltip": "บีบอัดพื้นผิว ASTC เพื่อลดการใช้งาน VRAM\n\nเกมที่ใช้รูปแบบพื้นผิวนี้ ได้แก่ Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder และ The Legend of Zelda: Tears of the Kingdom\n\nกราฟิกการ์ดที่มี 4 กิกะไบต์ VRAM หรือน้อยกว่ามีแนวโน้มที่จะให้แคชในบางจุดขณะเล่นเกมเหล่านี้\n\nเปิดใช้งานเฉพาะในกรณีที่ VRAM ของคุณใกล้หมดในเกมที่กล่าวมาข้างต้น ปล่อยให้ปิดหากไม่แน่ใจ", "SettingsTabGraphicsPreferredGpu": "GPU ที่ต้องการ", "SettingsTabGraphicsPreferredGpuTooltip": "เลือกกราฟิกการ์ดที่จะใช้กับแบ็กเอนด์กราฟิก Vulkan\n\nไม่ส่งผลต่อ GPU ที่ OpenGL จะใช้\n\nตั้งค่าเป็น GPU ที่ถูกตั้งค่าสถานะเป็น \"dGPU\" หากคุณไม่แน่ใจ หากไม่มีก็ปล่อยทิ้งไว้โดยไม่มีใครแตะต้องมัน", "SettingsAppRequiredRestartMessage": "จำเป็นต้องรีสตาร์ท รียูจินซ์", - "SettingsGpuBackendRestartMessage": "การตั้งค่ากราฟิกแบ็กเอนด์หรือ GPU ได้รับการแก้ไขแล้ว สิ่งนี้จะต้องมีการรีสตาร์ทจึงจะสามารถใช้งานได้", + "SettingsGpuBackendRestartMessage": "การตั้งค่ากราฟิกเบื้องหลังหรือ GPU ได้รับการแก้ไขแล้ว สิ่งนี้จะต้องมีการรีสตาร์ทจึงจะสามารถใช้งานได้", "SettingsGpuBackendRestartSubMessage": "คุณต้องการรีสตาร์ทตอนนี้หรือไม่?", "RyujinxUpdaterMessage": "คุณต้องการอัพเดต รียูจินซ์ เป็นเวอร์ชั่นล่าสุดหรือไม่?", "SettingsTabHotkeysVolumeUpHotkey": "เพิ่มระดับเสียง:", @@ -662,9 +769,9 @@ "SettingsTabNetworkInterface": "เชื่อมต่อเครือข่าย:", "NetworkInterfaceTooltip": "อินเทอร์เฟซเครือข่ายที่ใช้สำหรับคุณสมบัติ LAN/LDN\n\nเมื่อใช้ร่วมกับ VPN หรือ XLink Kai และเกมที่รองรับ LAN สามารถใช้เพื่อปลอมการเชื่อมต่อเครือข่ายเดียวกันผ่านทางอินเทอร์เน็ต\n\nปล่อยให้เป็น ค่าเริ่มต้น หากคุณไม่แน่ใจ", "NetworkInterfaceDefault": "ค่าเริ่มต้น", - "PackagingShaders": "แพ็คเชเดอร์ไฟล์", - "AboutChangelogButton": "ดูบันทึกการเปลี่ยนแปลงบน GitHub", - "AboutChangelogButtonTooltipMessage": "คลิกเพื่อเปิดบันทึกการเปลี่ยนแปลงสำหรับเวอร์ชั่นนี้ บนเบราว์เซอร์เริ่มต้นของคุณ", + "PackagingShaders": "รวม Shaders เข้าด้วยกัน", + "AboutChangelogButton": "ดูประวัติการเปลี่ยนแปลงบน GitHub", + "AboutChangelogButtonTooltipMessage": "คลิกเพื่อเปิดประวัติการเปลี่ยนแปลงสำหรับเวอร์ชั่นนี้ บนเบราว์เซอร์เริ่มต้นของคุณ", "SettingsTabNetworkMultiplayer": "ผู้เล่นหลายคน", "MultiplayerMode": "โหมด:", "MultiplayerModeTooltip": "เปลี่ยนโหมดผู้เล่นหลายคนของ LDN\n\nLdnMitm จะปรับเปลี่ยนฟังก์ชันการเล่นแบบไร้สาย/ภายใน จะให้เกมทำงานเหมือนกับว่าเป็น LAN ช่วยให้สามารถเชื่อมต่อภายในเครือข่ายเดียวกันกับอินสแตนซ์ Ryujinx อื่น ๆ และคอนโซล Nintendo Switch ที่ถูกแฮ็กซึ่งมีโมดูล ldn_mitm ติดตั้งอยู่\n\nผู้เล่นหลายคนต้องการให้ผู้เล่นทุกคนอยู่ในเกมเวอร์ชันเดียวกัน (เช่น Super Smash Bros. Ultimate v13.0.1 ไม่สามารถเชื่อมต่อกับ v13.0.0)\n\nปล่อยให้ปิดการใช้งานหากไม่แน่ใจ", diff --git a/src/Ryujinx/Assets/Locales/tr_TR.json b/src/Ryujinx/Assets/Locales/tr_TR.json index bfb5cb53a2..f74baaa182 100644 --- a/src/Ryujinx/Assets/Locales/tr_TR.json +++ b/src/Ryujinx/Assets/Locales/tr_TR.json @@ -30,6 +30,10 @@ "MenuBarToolsManageFileTypes": "Dosya uzantılarını yönet", "MenuBarToolsInstallFileTypes": "Dosya uzantılarını yükle", "MenuBarToolsUninstallFileTypes": "Dosya uzantılarını kaldır", + "MenuBarView": "_Görüntüle", + "MenuBarViewWindow": "Pencere Boyutu", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", "MenuBarHelp": "_Yardım", "MenuBarHelpCheckForUpdates": "Güncellemeleri Denetle", "MenuBarHelpAbout": "Hakkında", @@ -73,7 +77,7 @@ "GameListContextMenuCreateShortcut": "Uygulama Kısayolu Oluştur", "GameListContextMenuCreateShortcutToolTip": "Seçilmiş uygulamayı çalıştıracak bir masaüstü kısayolu oluştur", "GameListContextMenuCreateShortcutToolTipMacOS": "Create a shortcut in macOS's Applications folder that launches the selected Application", - "GameListContextMenuOpenModsDirectory": "Open Mods Directory", + "GameListContextMenuOpenModsDirectory": "Mod Dizinini Aç", "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", "GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory", "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", @@ -92,6 +96,7 @@ "SettingsTabGeneralEnableDiscordRichPresence": "Discord Zengin İçerik'i Etkinleştir", "SettingsTabGeneralCheckUpdatesOnLaunch": "Her Açılışta Güncellemeleri Denetle", "SettingsTabGeneralShowConfirmExitDialog": "\"Çıkışı Onayla\" Diyaloğunu Göster", + "SettingsTabGeneralRememberWindowState": "Remember Window Size/Position", "SettingsTabGeneralHideCursor": "İşaretçiyi Gizle:", "SettingsTabGeneralHideCursorNever": "Hiçbir Zaman", "SettingsTabGeneralHideCursorOnIdle": "Hareketsiz Durumda", @@ -155,7 +160,7 @@ "SettingsTabGraphicsResolutionScaleNative": "Yerel (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Not recommended)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Tavsiye Edilmez)", "SettingsTabGraphicsAspectRatio": "En-Boy Oranı:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -266,6 +271,107 @@ "ControllerSettingsMotionGyroDeadzone": "Gyro Ölü Bölgesi:", "ControllerSettingsSave": "Kaydet", "ControllerSettingsClose": "Kapat", + "KeyUnknown": "Unknown", + "KeyShiftLeft": "Sol Shift", + "KeyShiftRight": "Sağ Shift", + "KeyControlLeft": "Sol Ctrl", + "KeyMacControlLeft": "⌃ Sol", + "KeyControlRight": "Sağ Control", + "KeyMacControlRight": "⌃ Sağ", + "KeyAltLeft": "Sol Alt", + "KeyMacAltLeft": "⌥ Sol", + "KeyAltRight": "Sağ Alt", + "KeyMacAltRight": "⌥ Sağ", + "KeyWinLeft": "⊞ Sol", + "KeyMacWinLeft": "⌘ Sol", + "KeyWinRight": "⊞ Sağ", + "KeyMacWinRight": "⌘ Sağ", + "KeyMenu": "Menü", + "KeyUp": "Yukarı", + "KeyDown": "Aşağı", + "KeyLeft": "Sol", + "KeyRight": "Sağ", + "KeyEnter": "Enter", + "KeyEscape": "Esc", + "KeySpace": "Space", + "KeyTab": "Tab", + "KeyBackSpace": "Geri tuşu", + "KeyInsert": "Insert", + "KeyDelete": "Delete", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "Print Screen", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "Clear", + "KeyKeypad0": "Keypad 0", + "KeyKeypad1": "Keypad 1", + "KeyKeypad2": "Keypad 2", + "KeyKeypad3": "Keypad 3", + "KeyKeypad4": "Keypad 4", + "KeyKeypad5": "Keypad 5", + "KeyKeypad6": "Keypad 6", + "KeyKeypad7": "Keypad 7", + "KeyKeypad8": "Keypad 8", + "KeyKeypad9": "Keypad 9", + "KeyKeypadDivide": "Keypad Divide", + "KeyKeypadMultiply": "Keypad Multiply", + "KeyKeypadSubtract": "Keypad Subtract", + "KeyKeypadAdd": "Keypad Add", + "KeyKeypadDecimal": "Keypad Decimal", + "KeyKeypadEnter": "Keypad Enter", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "Unbound", + "GamepadLeftStick": "L Stick Button", + "GamepadRightStick": "R Stick Button", + "GamepadLeftShoulder": "Left Shoulder", + "GamepadRightShoulder": "Right Shoulder", + "GamepadLeftTrigger": "Left Trigger", + "GamepadRightTrigger": "Right Trigger", + "GamepadDpadUp": "Up", + "GamepadDpadDown": "Down", + "GamepadDpadLeft": "Left", + "GamepadDpadRight": "Sağ", + "GamepadMinus": "-", + "GamepadPlus": "4", + "GamepadGuide": "Rehber", + "GamepadMisc1": "Diğer", + "GamepadPaddle1": "Pedal 1", + "GamepadPaddle2": "Pedal 2", + "GamepadPaddle3": "Pedal 3", + "GamepadPaddle4": "Pedal 4", + "GamepadTouchpad": "Touchpad", + "GamepadSingleLeftTrigger0": "Sol Tetik 0", + "GamepadSingleRightTrigger0": "Sağ Tetik 0", + "GamepadSingleLeftTrigger1": "Sol Tetik 1", + "GamepadSingleRightTrigger1": "Sağ Tetik 1", + "StickLeft": "Sol Çubuk", + "StickRight": "Sağ çubuk", "UserProfilesSelectedUserProfile": "Seçili Kullanıcı Profili:", "UserProfilesSaveProfileName": "Profil İsmini Kaydet", "UserProfilesChangeProfileImage": "Profil Resmini Değiştir", @@ -297,9 +403,9 @@ "GameListContextMenuRunApplication": "Uygulamayı Çalıştır", "GameListContextMenuToggleFavorite": "Favori Ayarla", "GameListContextMenuToggleFavoriteToolTip": "Oyunu Favorilere Ekle/Çıkar", - "SettingsTabGeneralTheme": "Theme:", - "SettingsTabGeneralThemeDark": "Dark", - "SettingsTabGeneralThemeLight": "Light", + "SettingsTabGeneralTheme": "Tema:", + "SettingsTabGeneralThemeDark": "Karanlık", + "SettingsTabGeneralThemeLight": "Aydınlık", "ControllerSettingsConfigureGeneral": "Ayarla", "ControllerSettingsRumble": "Titreşim", "ControllerSettingsRumbleStrongMultiplier": "Güçlü Titreşim Çoklayıcı", @@ -384,10 +490,10 @@ "DialogUserProfileUnsavedChangesSubMessage": "Yaptığınız değişiklikleri iptal etmek istediğinize emin misiniz?", "DialogControllerSettingsModifiedConfirmMessage": "Geçerli kumanda seçenekleri güncellendi.", "DialogControllerSettingsModifiedConfirmSubMessage": "Kaydetmek istiyor musunuz?", - "DialogLoadFileErrorMessage": "{0}. Errored File: {1}", - "DialogModAlreadyExistsMessage": "Mod already exists", + "DialogLoadFileErrorMessage": "{0}. Hatalı Dosya: {1}", + "DialogModAlreadyExistsMessage": "Mod zaten var", "DialogModInvalidMessage": "The specified directory does not contain a mod!", - "DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!", + "DialogModDeleteNoParentMessage": "Silme Başarısız: \"{0}\" Modu için üst dizin bulunamadı! ", "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?", @@ -434,7 +540,7 @@ "DlcManagerRemoveAllButton": "Tümünü kaldır", "DlcManagerEnableAllButton": "Tümünü Aktif Et", "DlcManagerDisableAllButton": "Tümünü Devre Dışı Bırak", - "ModManagerDeleteAllButton": "Delete All", + "ModManagerDeleteAllButton": "Hepsini Sil", "MenuBarOptionsChangeLanguage": "Dili Değiştir", "MenuBarShowFileTypes": "Dosya Uzantılarını Göster", "CommonSort": "Sırala", @@ -509,8 +615,8 @@ "EnableInternetAccessTooltip": "Emüle edilen uygulamanın internete bağlanmasını sağlar.\n\nLAN modu bulunan oyunlar bu seçenek ile birbirine bağlanabilir ve sistemler aynı access point'e bağlanır. Bu gerçek konsolları da kapsar.\n\nNintendo sunucularına bağlanmayı sağlaMAZ. Internete bağlanmaya çalışan baz oyunların çökmesine sebep olabilr.\n\nEmin değilseniz devre dışı bırakın.", "GameListContextMenuManageCheatToolTip": "Hileleri yönetmeyi sağlar", "GameListContextMenuManageCheat": "Hileleri Yönet", - "GameListContextMenuManageModToolTip": "Manage Mods", - "GameListContextMenuManageMod": "Manage Mods", + "GameListContextMenuManageModToolTip": "Modları Yönet", + "GameListContextMenuManageMod": "Modları Yönet", "ControllerSettingsStickRange": "Menzil:", "DialogStopEmulationTitle": "Ryujinx - Emülasyonu Durdur", "DialogStopEmulationMessage": "Emülasyonu durdurmak istediğinizden emin misiniz?", @@ -593,15 +699,16 @@ "Writable": "Yazılabilir", "SelectDlcDialogTitle": "DLC dosyalarını seç", "SelectUpdateDialogTitle": "Güncelleme dosyalarını seç", - "SelectModDialogTitle": "Select mod directory", + "SelectModDialogTitle": "Mod Dizinini Seç", "UserProfileWindowTitle": "Kullanıcı Profillerini Yönet", "CheatWindowTitle": "Oyun Hilelerini Yönet", "DlcWindowTitle": "Oyun DLC'lerini Yönet", + "ModWindowTitle": "Manage Mods for {0} ({1})", "UpdateWindowTitle": "Oyun Güncellemelerini Yönet", "CheatWindowHeading": "{0} için Hile mevcut [{1}]", "BuildId": "BuildId:", "DlcWindowHeading": "{0} için DLC mevcut [{1}]", - "ModWindowHeading": "{0} Mod(s)", + "ModWindowHeading": "{0} Mod(lar)", "UserProfilesEditProfile": "Seçiliyi Düzenle", "Cancel": "İptal", "Save": "Kaydet", @@ -668,6 +775,6 @@ "SettingsTabNetworkMultiplayer": "Çok Oyunculu", "MultiplayerMode": "Mod:", "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.", - "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeDisabled": "Devre Dışı", "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/uk_UA.json b/src/Ryujinx/Assets/Locales/uk_UA.json index dcf85eae9e..976edfb1b3 100644 --- a/src/Ryujinx/Assets/Locales/uk_UA.json +++ b/src/Ryujinx/Assets/Locales/uk_UA.json @@ -30,6 +30,10 @@ "MenuBarToolsManageFileTypes": "Керувати типами файлів", "MenuBarToolsInstallFileTypes": "Установити типи файлів", "MenuBarToolsUninstallFileTypes": "Видалити типи файлів", + "MenuBarView": "_View", + "MenuBarViewWindow": "Window Size", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", "MenuBarHelp": "_Допомога", "MenuBarHelpCheckForUpdates": "Перевірити оновлення", "MenuBarHelpAbout": "Про застосунок", @@ -92,6 +96,7 @@ "SettingsTabGeneralEnableDiscordRichPresence": "Увімкнути розширену присутність Discord", "SettingsTabGeneralCheckUpdatesOnLaunch": "Перевіряти наявність оновлень під час запуску", "SettingsTabGeneralShowConfirmExitDialog": "Показати діалогове вікно «Підтвердити вихід».", + "SettingsTabGeneralRememberWindowState": "Remember Window Size/Position", "SettingsTabGeneralHideCursor": "Сховати вказівник:", "SettingsTabGeneralHideCursorNever": "Ніколи", "SettingsTabGeneralHideCursorOnIdle": "Сховати у режимі очікування", @@ -266,6 +271,107 @@ "ControllerSettingsMotionGyroDeadzone": "Мертва зона гіроскопа:", "ControllerSettingsSave": "Зберегти", "ControllerSettingsClose": "Закрити", + "KeyUnknown": "Unknown", + "KeyShiftLeft": "Shift Left", + "KeyShiftRight": "Shift Right", + "KeyControlLeft": "Ctrl Left", + "KeyMacControlLeft": "⌃ Left", + "KeyControlRight": "Ctrl Right", + "KeyMacControlRight": "⌃ Right", + "KeyAltLeft": "Alt Left", + "KeyMacAltLeft": "⌥ Left", + "KeyAltRight": "Alt Right", + "KeyMacAltRight": "⌥ Right", + "KeyWinLeft": "⊞ Left", + "KeyMacWinLeft": "⌘ Left", + "KeyWinRight": "⊞ Right", + "KeyMacWinRight": "⌘ Right", + "KeyMenu": "Menu", + "KeyUp": "Up", + "KeyDown": "Down", + "KeyLeft": "Left", + "KeyRight": "Right", + "KeyEnter": "Enter", + "KeyEscape": "Escape", + "KeySpace": "Space", + "KeyTab": "Tab", + "KeyBackSpace": "Backspace", + "KeyInsert": "Insert", + "KeyDelete": "Delete", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "Print Screen", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "Clear", + "KeyKeypad0": "Keypad 0", + "KeyKeypad1": "Keypad 1", + "KeyKeypad2": "Keypad 2", + "KeyKeypad3": "Keypad 3", + "KeyKeypad4": "Keypad 4", + "KeyKeypad5": "Keypad 5", + "KeyKeypad6": "Keypad 6", + "KeyKeypad7": "Keypad 7", + "KeyKeypad8": "Keypad 8", + "KeyKeypad9": "Keypad 9", + "KeyKeypadDivide": "Keypad Divide", + "KeyKeypadMultiply": "Keypad Multiply", + "KeyKeypadSubtract": "Keypad Subtract", + "KeyKeypadAdd": "Keypad Add", + "KeyKeypadDecimal": "Keypad Decimal", + "KeyKeypadEnter": "Keypad Enter", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "Unbound", + "GamepadLeftStick": "L Stick Button", + "GamepadRightStick": "R Stick Button", + "GamepadLeftShoulder": "Left Shoulder", + "GamepadRightShoulder": "Right Shoulder", + "GamepadLeftTrigger": "Left Trigger", + "GamepadRightTrigger": "Right Trigger", + "GamepadDpadUp": "Up", + "GamepadDpadDown": "Down", + "GamepadDpadLeft": "Left", + "GamepadDpadRight": "Right", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "Guide", + "GamepadMisc1": "Misc", + "GamepadPaddle1": "Paddle 1", + "GamepadPaddle2": "Paddle 2", + "GamepadPaddle3": "Paddle 3", + "GamepadPaddle4": "Paddle 4", + "GamepadTouchpad": "Touchpad", + "GamepadSingleLeftTrigger0": "Left Trigger 0", + "GamepadSingleRightTrigger0": "Right Trigger 0", + "GamepadSingleLeftTrigger1": "Left Trigger 1", + "GamepadSingleRightTrigger1": "Right Trigger 1", + "StickLeft": "Left Stick", + "StickRight": "Right Stick", "UserProfilesSelectedUserProfile": "Вибраний профіль користувача:", "UserProfilesSaveProfileName": "Зберегти ім'я профілю", "UserProfilesChangeProfileImage": "Змінити зображення профілю", @@ -597,6 +703,7 @@ "UserProfileWindowTitle": "Менеджер профілів користувачів", "CheatWindowTitle": "Менеджер читів", "DlcWindowTitle": "Менеджер вмісту для завантаження", + "ModWindowTitle": "Керувати модами для {0} ({1})", "UpdateWindowTitle": "Менеджер оновлення назв", "CheatWindowHeading": "Коди доступні для {0} [{1}]", "BuildId": "ID збірки:", diff --git a/src/Ryujinx/Assets/Locales/zh_CN.json b/src/Ryujinx/Assets/Locales/zh_CN.json index cc1d583e10..66f59ecd04 100644 --- a/src/Ryujinx/Assets/Locales/zh_CN.json +++ b/src/Ryujinx/Assets/Locales/zh_CN.json @@ -8,29 +8,33 @@ "SettingsTabSystemMemoryManagerModeHost": "本机映射 (较快)", "SettingsTabSystemMemoryManagerModeHostUnchecked": "跳过检查的本机映射 (最快,但不安全)", "SettingsTabSystemUseHypervisor": "使用 Hypervisor 虚拟化", - "MenuBarFile": "文件", - "MenuBarFileOpenFromFile": "加载游戏文件", - "MenuBarFileOpenUnpacked": "加载解包后的游戏", + "MenuBarFile": "文件(_F)", + "MenuBarFileOpenFromFile": "加载游戏文件(_L)", + "MenuBarFileOpenUnpacked": "加载解包后的游戏(_U)", "MenuBarFileOpenEmuFolder": "打开 Ryujinx 系统目录", "MenuBarFileOpenLogsFolder": "打开日志目录", - "MenuBarFileExit": "退出", - "MenuBarOptions": "选项", + "MenuBarFileExit": "退出(_E)", + "MenuBarOptions": "选项(_O)", "MenuBarOptionsToggleFullscreen": "切换全屏", "MenuBarOptionsStartGamesInFullscreen": "全屏模式启动游戏", "MenuBarOptionsStopEmulation": "停止模拟", - "MenuBarOptionsSettings": "设置", - "MenuBarOptionsManageUserProfiles": "管理用户账户", - "MenuBarActions": "操作", + "MenuBarOptionsSettings": "设置(_S)", + "MenuBarOptionsManageUserProfiles": "管理用户账户(_M)", + "MenuBarActions": "操作(_A)", "MenuBarOptionsSimulateWakeUpMessage": "模拟唤醒消息", "MenuBarActionsScanAmiibo": "扫描 Amiibo", - "MenuBarTools": "工具", + "MenuBarTools": "工具(_T)", "MenuBarToolsInstallFirmware": "安装系统固件", "MenuBarFileToolsInstallFirmwareFromFile": "从 XCI 或 ZIP 文件中安装系统固件", "MenuBarFileToolsInstallFirmwareFromDirectory": "从文件夹中安装系统固件", "MenuBarToolsManageFileTypes": "管理文件扩展名", "MenuBarToolsInstallFileTypes": "关联文件扩展名", "MenuBarToolsUninstallFileTypes": "取消关联扩展名", - "MenuBarHelp": "帮助", + "MenuBarView": "视图(_V)", + "MenuBarViewWindow": "窗口大小", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", + "MenuBarHelp": "帮助(_H)", "MenuBarHelpCheckForUpdates": "检查更新", "MenuBarHelpAbout": "关于", "MenuSearch": "搜索…", @@ -92,6 +96,7 @@ "SettingsTabGeneralEnableDiscordRichPresence": "启用 Discord 在线状态展示", "SettingsTabGeneralCheckUpdatesOnLaunch": "启动时检查更新", "SettingsTabGeneralShowConfirmExitDialog": "退出游戏时需要确认", + "SettingsTabGeneralRememberWindowState": "记住窗口大小和位置", "SettingsTabGeneralHideCursor": "隐藏鼠标指针:", "SettingsTabGeneralHideCursorNever": "从不隐藏", "SettingsTabGeneralHideCursorOnIdle": "自动隐藏", @@ -266,6 +271,107 @@ "ControllerSettingsMotionGyroDeadzone": "陀螺仪死区:", "ControllerSettingsSave": "保存", "ControllerSettingsClose": "关闭", + "KeyUnknown": "未知", + "KeyShiftLeft": "左侧Shift", + "KeyShiftRight": "右侧Shift", + "KeyControlLeft": "左侧Ctrl", + "KeyMacControlLeft": "左侧⌃", + "KeyControlRight": "右侧Ctrl", + "KeyMacControlRight": "右侧⌃", + "KeyAltLeft": "左侧Alt", + "KeyMacAltLeft": "左侧⌥", + "KeyAltRight": "右侧Alt", + "KeyMacAltRight": "右侧⌥", + "KeyWinLeft": "左侧⊞", + "KeyMacWinLeft": "左侧⌘", + "KeyWinRight": "右侧⊞", + "KeyMacWinRight": "右侧⌘", + "KeyMenu": "菜单键", + "KeyUp": "上", + "KeyDown": "下", + "KeyLeft": "左", + "KeyRight": "右", + "KeyEnter": "回车键", + "KeyEscape": "Esc", + "KeySpace": "空格键", + "KeyTab": "Tab", + "KeyBackSpace": "退格键", + "KeyInsert": "Insert", + "KeyDelete": "Delete", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "Print Screen", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "清除键", + "KeyKeypad0": "小键盘0", + "KeyKeypad1": "小键盘1", + "KeyKeypad2": "小键盘2", + "KeyKeypad3": "小键盘3", + "KeyKeypad4": "小键盘4", + "KeyKeypad5": "小键盘5", + "KeyKeypad6": "小键盘6", + "KeyKeypad7": "小键盘7", + "KeyKeypad8": "小键盘8", + "KeyKeypad9": "小键盘9", + "KeyKeypadDivide": "小键盘/", + "KeyKeypadMultiply": "小键盘*", + "KeyKeypadSubtract": "小键盘-", + "KeyKeypadAdd": "小键盘+", + "KeyKeypadDecimal": "小键盘.", + "KeyKeypadEnter": "小键盘回车键", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "未分配", + "GamepadLeftStick": "左摇杆按键", + "GamepadRightStick": "右摇杆按键", + "GamepadLeftShoulder": "左肩键L", + "GamepadRightShoulder": "右肩键R", + "GamepadLeftTrigger": "左扳机键ZL", + "GamepadRightTrigger": "右扳机键ZR", + "GamepadDpadUp": "上键", + "GamepadDpadDown": "下键", + "GamepadDpadLeft": "左键", + "GamepadDpadRight": "右键", + "GamepadMinus": "-键", + "GamepadPlus": "+键", + "GamepadGuide": "主页键", + "GamepadMisc1": "截图键", + "GamepadPaddle1": "其他按键1", + "GamepadPaddle2": "其他按键2", + "GamepadPaddle3": "其他按键3", + "GamepadPaddle4": "其他按键4", + "GamepadTouchpad": "触摸板", + "GamepadSingleLeftTrigger0": "左扳机0", + "GamepadSingleRightTrigger0": "右扳机0", + "GamepadSingleLeftTrigger1": "左扳机1", + "GamepadSingleRightTrigger1": "右扳机1", + "StickLeft": "左摇杆", + "StickRight": "右摇杆", "UserProfilesSelectedUserProfile": "选定的用户账户:", "UserProfilesSaveProfileName": "保存名称", "UserProfilesChangeProfileImage": "更换头像", @@ -597,6 +703,7 @@ "UserProfileWindowTitle": "管理用户账户", "CheatWindowTitle": "金手指管理器", "DlcWindowTitle": "管理 {0} ({1}) 的 DLC", + "ModWindowTitle": "管理 {0} ({1}) 的 MOD", "UpdateWindowTitle": "游戏更新管理器", "CheatWindowHeading": "适用于 {0} [{1}] 的金手指", "BuildId": "游戏版本 ID:", diff --git a/src/Ryujinx/Assets/Locales/zh_TW.json b/src/Ryujinx/Assets/Locales/zh_TW.json index 301a558560..fc838d2510 100644 --- a/src/Ryujinx/Assets/Locales/zh_TW.json +++ b/src/Ryujinx/Assets/Locales/zh_TW.json @@ -10,7 +10,7 @@ "SettingsTabSystemUseHypervisor": "使用 Hypervisor", "MenuBarFile": "檔案(_F)", "MenuBarFileOpenFromFile": "從檔案載入應用程式(_L)", - "MenuBarFileOpenUnpacked": "載入解開封裝的遊戲(_U)", + "MenuBarFileOpenUnpacked": "載入未封裝的遊戲(_U)", "MenuBarFileOpenEmuFolder": "開啟 Ryujinx 資料夾", "MenuBarFileOpenLogsFolder": "開啟日誌資料夾", "MenuBarFileExit": "結束(_E)", @@ -30,6 +30,10 @@ "MenuBarToolsManageFileTypes": "管理檔案類型", "MenuBarToolsInstallFileTypes": "安裝檔案類型", "MenuBarToolsUninstallFileTypes": "移除檔案類型", + "MenuBarView": "檢視(_V)", + "MenuBarViewWindow": "視窗大小", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", "MenuBarHelp": "說明(_H)", "MenuBarHelpCheckForUpdates": "檢查更新", "MenuBarHelpAbout": "關於", @@ -92,6 +96,7 @@ "SettingsTabGeneralEnableDiscordRichPresence": "啟用 Discord 動態狀態展示", "SettingsTabGeneralCheckUpdatesOnLaunch": "啟動時檢查更新", "SettingsTabGeneralShowConfirmExitDialog": "顯示「確認結束」對話方塊", + "SettingsTabGeneralRememberWindowState": "記住視窗大小/位置", "SettingsTabGeneralHideCursor": "隱藏滑鼠游標:", "SettingsTabGeneralHideCursorNever": "從不", "SettingsTabGeneralHideCursorOnIdle": "閒置時", @@ -144,13 +149,13 @@ "SettingsTabGraphics": "圖形", "SettingsTabGraphicsAPI": "圖形 API", "SettingsTabGraphicsEnableShaderCache": "啟用著色器快取", - "SettingsTabGraphicsAnisotropicFiltering": "各向異性過濾:", + "SettingsTabGraphicsAnisotropicFiltering": "各向異性過濾:", "SettingsTabGraphicsAnisotropicFilteringAuto": "自動", "SettingsTabGraphicsAnisotropicFiltering2x": "2 倍", "SettingsTabGraphicsAnisotropicFiltering4x": "4 倍", "SettingsTabGraphicsAnisotropicFiltering8x": "8 倍", "SettingsTabGraphicsAnisotropicFiltering16x": "16 倍", - "SettingsTabGraphicsResolutionScale": "解析度比例:", + "SettingsTabGraphicsResolutionScale": "解析度比例:", "SettingsTabGraphicsResolutionScaleCustom": "自訂 (不建議使用)", "SettingsTabGraphicsResolutionScaleNative": "原生 (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2 倍 (1440p/2160p)", @@ -164,7 +169,7 @@ "SettingsTabGraphicsAspectRatio32x9": "32:9", "SettingsTabGraphicsAspectRatioStretch": "拉伸以適應視窗", "SettingsTabGraphicsDeveloperOptions": "開發者選項", - "SettingsTabGraphicsShaderDumpPath": "圖形著色器傾印路徑:", + "SettingsTabGraphicsShaderDumpPath": "圖形著色器傾印路徑:", "SettingsTabLogging": "日誌", "SettingsTabLoggingLogging": "日誌", "SettingsTabLoggingEnableLoggingToFile": "啟用日誌到檔案", @@ -252,8 +257,8 @@ "ControllerSettingsLeftSR": "SR", "ControllerSettingsRightSL": "SL", "ControllerSettingsRightSR": "SR", - "ControllerSettingsExtraButtonsLeft": "左按鍵", - "ControllerSettingsExtraButtonsRight": "右按鍵", + "ControllerSettingsExtraButtonsLeft": "左背鍵", + "ControllerSettingsExtraButtonsRight": "右背鍵", "ControllerSettingsMisc": "其他", "ControllerSettingsTriggerThreshold": "扳機閾值:", "ControllerSettingsMotion": "體感", @@ -266,6 +271,107 @@ "ControllerSettingsMotionGyroDeadzone": "陀螺儀無感帶:", "ControllerSettingsSave": "儲存", "ControllerSettingsClose": "關閉", + "KeyUnknown": "未知", + "KeyShiftLeft": "左 Shift", + "KeyShiftRight": "右 Shift", + "KeyControlLeft": "左 Ctrl", + "KeyMacControlLeft": "左 ⌃", + "KeyControlRight": "右 Ctrl", + "KeyMacControlRight": "右 ⌃", + "KeyAltLeft": "左 Alt", + "KeyMacAltLeft": "左 ⌥", + "KeyAltRight": "右 Alt", + "KeyMacAltRight": "右 ⌥", + "KeyWinLeft": "左 ⊞", + "KeyMacWinLeft": "左 ⌘", + "KeyWinRight": "右 ⊞", + "KeyMacWinRight": "右 ⌘", + "KeyMenu": "功能表", + "KeyUp": "上", + "KeyDown": "下", + "KeyLeft": "左", + "KeyRight": "右", + "KeyEnter": "Enter 鍵", + "KeyEscape": "Esc 鍵", + "KeySpace": "空白鍵", + "KeyTab": "Tab 鍵", + "KeyBackSpace": "Backspace 鍵", + "KeyInsert": "Insert 鍵", + "KeyDelete": "Delete 鍵", + "KeyPageUp": "向上捲頁鍵", + "KeyPageDown": "向下捲頁鍵", + "KeyHome": "Home 鍵", + "KeyEnd": "End 鍵", + "KeyCapsLock": "Caps Lock 鍵", + "KeyScrollLock": "Scroll Lock 鍵", + "KeyPrintScreen": "Print Screen 鍵", + "KeyPause": "Pause 鍵", + "KeyNumLock": "Num Lock 鍵", + "KeyClear": "清除", + "KeyKeypad0": "數字鍵 0", + "KeyKeypad1": "數字鍵 1", + "KeyKeypad2": "數字鍵 2", + "KeyKeypad3": "數字鍵 3", + "KeyKeypad4": "數字鍵 4", + "KeyKeypad5": "數字鍵 5", + "KeyKeypad6": "數字鍵 6", + "KeyKeypad7": "數字鍵 7", + "KeyKeypad8": "數字鍵 8", + "KeyKeypad9": "數字鍵 9", + "KeyKeypadDivide": "數字鍵除號", + "KeyKeypadMultiply": "數字鍵乘號", + "KeyKeypadSubtract": "數字鍵減號", + "KeyKeypadAdd": "數字鍵加號", + "KeyKeypadDecimal": "數字鍵小數點", + "KeyKeypadEnter": "數字鍵 Enter", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "未分配", + "GamepadLeftStick": "左搖桿按鍵", + "GamepadRightStick": "右搖桿按鍵", + "GamepadLeftShoulder": "左肩鍵", + "GamepadRightShoulder": "右肩鍵", + "GamepadLeftTrigger": "左扳機", + "GamepadRightTrigger": "右扳機", + "GamepadDpadUp": "上", + "GamepadDpadDown": "下", + "GamepadDpadLeft": "左", + "GamepadDpadRight": "右", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "快顯功能表鍵", + "GamepadMisc1": "其他按鍵", + "GamepadPaddle1": "其他按鍵 1", + "GamepadPaddle2": "其他按鍵 2", + "GamepadPaddle3": "其他按鍵 3", + "GamepadPaddle4": "其他按鍵 4", + "GamepadTouchpad": "觸控板", + "GamepadSingleLeftTrigger0": "左扳機 0", + "GamepadSingleRightTrigger0": "右扳機 0", + "GamepadSingleLeftTrigger1": "左扳機 1", + "GamepadSingleRightTrigger1": "右扳機 1", + "StickLeft": "左搖桿", + "StickRight": "右搖桿", "UserProfilesSelectedUserProfile": "選取的使用者設定檔:", "UserProfilesSaveProfileName": "儲存設定檔名稱", "UserProfilesChangeProfileImage": "變更設定檔圖像", @@ -490,7 +596,7 @@ "OpenGlLogLevel": "需要啟用適當的日誌等級", "DebugLogTooltip": "在控制台中輸出偵錯日誌訊息。\n\n只有在人員特別指示的情況下才能使用,因為這會導致日誌難以閱讀,並降低模擬器效能。", "LoadApplicationFileTooltip": "開啟檔案總管,選擇與 Switch 相容的檔案來載入", - "LoadApplicationFolderTooltip": "開啟檔案總管,選擇與 Switch 相容且解開封裝的應用程式來載入", + "LoadApplicationFolderTooltip": "開啟檔案總管,選擇與 Switch 相容且未封裝的應用程式來載入", "OpenRyujinxFolderTooltip": "開啟 Ryujinx 檔案系統資料夾", "OpenRyujinxLogsTooltip": "開啟日誌被寫入的資料夾", "ExitTooltip": "結束 Ryujinx", @@ -550,7 +656,7 @@ "SwkbdMinRangeCharacters": "長度必須為 {0} 到 {1} 個字元", "SoftwareKeyboard": "軟體鍵盤", "SoftwareKeyboardModeNumeric": "必須是 0 到 9 或「.」", - "SoftwareKeyboardModeAlphabet": "必須是非「中日韓字元」 (non CJK)", + "SoftwareKeyboardModeAlphabet": "必須是「非中日韓字元」 (non CJK)", "SoftwareKeyboardModeASCII": "必須是 ASCII 文字", "ControllerAppletControllers": "支援的控制器:", "ControllerAppletPlayers": "玩家:", @@ -572,7 +678,7 @@ "CompilingShaders": "正在編譯著色器", "AllKeyboards": "所有鍵盤", "OpenFileDialogTitle": "選取支援的檔案格式", - "OpenFolderDialogTitle": "選取解開封裝遊戲的資料夾", + "OpenFolderDialogTitle": "選取未封裝遊戲的資料夾", "AllSupportedFormats": "所有支援的格式", "RyujinxUpdater": "Ryujinx 更新程式", "SettingsTabHotkeys": "鍵盤快速鍵", @@ -597,6 +703,7 @@ "UserProfileWindowTitle": "使用者設定檔管理員", "CheatWindowTitle": "密技管理員", "DlcWindowTitle": "管理 {0} 的可下載內容 ({1})", + "ModWindowTitle": "管理 {0} 的模組 ({1})", "UpdateWindowTitle": "遊戲更新管理員", "CheatWindowHeading": "可用於 {0} [{1}] 的密技", "BuildId": "組建識別碼:", @@ -610,7 +717,7 @@ "UserProfilesSetProfileImage": "設定設定檔圖像", "UserProfileEmptyNameError": "名稱為必填", "UserProfileNoImageError": "必須設定設定檔圖像", - "GameUpdateWindowHeading": "可用於 {0} ({1}) 的更新", + "GameUpdateWindowHeading": "管理 {0} 的更新 ({1})", "SettingsTabHotkeysResScaleUpHotkey": "提高解析度:", "SettingsTabHotkeysResScaleDownHotkey": "降低解析度:", "UserProfilesName": "名稱:", @@ -635,7 +742,7 @@ "UserProfilesManageSaves": "管理存檔", "DeleteUserSave": "您想刪除此遊戲的使用者存檔嗎?", "IrreversibleActionNote": "此動作將無法復原。", - "SaveManagerHeading": "管理 {0} 的存檔", + "SaveManagerHeading": "管理 {0} 的存檔 ({1})", "SaveManagerTitle": "存檔管理員", "Name": "名稱", "Size": "大小", @@ -647,9 +754,9 @@ "GraphicsAATooltip": "對遊戲繪製進行反鋸齒處理。\n\nFXAA 會模糊大部分圖像,而 SMAA 則會嘗試找出鋸齒邊緣並將其平滑化。\n\n不建議與 FSR 縮放濾鏡一起使用。\n\n此選項可在遊戲執行時透過點選下方的「套用」進行變更;您只需將設定視窗移到一旁,然後進行試驗,直到找到您喜歡的遊戲效果。\n\n如果不確定,請選擇無狀態。", "GraphicsAALabel": "反鋸齒:", "GraphicsScalingFilterLabel": "縮放過濾器:", - "GraphicsScalingFilterTooltip": "選擇使用解析度縮放時套用的縮放過濾器。\n\nBilinear (雙線性) 濾鏡適用於 3D 遊戲,是一個安全的預設選項。\n\n建議像素美術遊戲使用 Nearest (最近性) 濾鏡。\n\nFSR 1.0 只是一個銳化濾鏡,不建議與 FXAA 或 SMAA 一起使用。\n\n此選項可在遊戲執行時透過點選下方的「套用」進行變更;您只需將設定視窗移到一旁,然後進行試驗,直到找到您喜歡的遊戲效果。\n\n如果不確定,請保持 Bilinear (雙線) 狀態。", - "GraphicsScalingFilterBilinear": "Bilinear", - "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterTooltip": "選擇使用解析度縮放時套用的縮放過濾器。\n\n雙線性 (Bilinear) 濾鏡適用於 3D 遊戲,是一個安全的預設選項。\n\n建議像素美術遊戲使用近鄰性 (Nearest) 濾鏡。\n\nFSR 1.0 只是一個銳化濾鏡,不建議與 FXAA 或 SMAA 一起使用。\n\n此選項可在遊戲執行時透過點選下方的「套用」進行變更;您只需將設定視窗移到一旁,然後進行試驗,直到找到您喜歡的遊戲效果。\n\n如果不確定,請保持雙線性 (Bilinear) 狀態。", + "GraphicsScalingFilterBilinear": "雙線性 (Bilinear)", + "GraphicsScalingFilterNearest": "近鄰性 (Nearest)", "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "日誌等級", "GraphicsScalingFilterLevelTooltip": "設定 FSR 1.0 銳化等級。越高越清晰。", @@ -662,12 +769,12 @@ "SettingsTabNetworkInterface": "網路介面:", "NetworkInterfaceTooltip": "用於 LAN/LDN 功能的網路介面。\n\n與 VPN 或 XLink Kai 以及支援區域網路的遊戲配合使用,可用於在網路上偽造同網際網路連線。\n\n如果不確定,請保持預設狀態。", "NetworkInterfaceDefault": "預設", - "PackagingShaders": "著色器封裝", + "PackagingShaders": "封裝著色器", "AboutChangelogButton": "在 GitHub 上檢視更新日誌", "AboutChangelogButtonTooltipMessage": "在預設瀏覽器中開啟此版本的更新日誌。", "SettingsTabNetworkMultiplayer": "多人遊戲", "MultiplayerMode": "模式:", "MultiplayerModeTooltip": "變更 LDN 多人遊戲模式。\n\nLdnMitm 將修改遊戲中的本機無線/本機遊戲功能,使其如同區域網路一樣執行,允許與其他安裝了 ldn_mitm 模組的 Ryujinx 實例和已破解的 Nintendo Switch 遊戲機進行本機同網路連線。\n\n多人遊戲要求所有玩家使用相同的遊戲版本 (例如,Super Smash Bros. Ultimate v13.0.1 無法連接 v13.0.0)。\n\n如果不確定,請保持 Disabled (停用) 狀態。", - "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeDisabled": "已停用", "MultiplayerModeLdnMitm": "ldn_mitm" } From cdccf89e103694dcad3833d900b7858a49dae1ec Mon Sep 17 00:00:00 2001 From: gdkchan Date: Wed, 15 May 2024 02:20:24 -0300 Subject: [PATCH 85/87] =?UTF-8?q?Revert=20"Disable=20keyboard=20controller?= =?UTF-8?q?=20input=20while=20swkbd=20is=20open=20(foreground)=20(#?= =?UTF-8?q?=E2=80=A6"=20(#6805)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit a3dc295c5f867bddb56a38f3a848ceb61ff30d32. --- src/Ryujinx.Gtk3/Input/GTK3/GTK3KeyboardDriver.cs | 5 ----- src/Ryujinx.Gtk3/UI/Applet/GtkHostUIHandler.cs | 3 --- src/Ryujinx.Input/HLE/NpadManager.cs | 5 ----- src/Ryujinx.Input/IGamepadDriver.cs | 6 ------ src/Ryujinx/Input/AvaloniaKeyboard.cs | 2 +- src/Ryujinx/Input/AvaloniaKeyboardDriver.cs | 2 +- src/Ryujinx/UI/Applet/AvaHostUIHandler.cs | 2 -- 7 files changed, 2 insertions(+), 23 deletions(-) diff --git a/src/Ryujinx.Gtk3/Input/GTK3/GTK3KeyboardDriver.cs b/src/Ryujinx.Gtk3/Input/GTK3/GTK3KeyboardDriver.cs index bd71c7933f..e502254be1 100644 --- a/src/Ryujinx.Gtk3/Input/GTK3/GTK3KeyboardDriver.cs +++ b/src/Ryujinx.Gtk3/Input/GTK3/GTK3KeyboardDriver.cs @@ -81,11 +81,6 @@ namespace Ryujinx.Input.GTK3 return _pressedKeys.Contains(nativeKey); } - public void Clear() - { - _pressedKeys.Clear(); - } - public IGamepad GetGamepad(string id) { if (!_keyboardIdentifers[0].Equals(id)) diff --git a/src/Ryujinx.Gtk3/UI/Applet/GtkHostUIHandler.cs b/src/Ryujinx.Gtk3/UI/Applet/GtkHostUIHandler.cs index b3f509a090..1d918d21b6 100644 --- a/src/Ryujinx.Gtk3/UI/Applet/GtkHostUIHandler.cs +++ b/src/Ryujinx.Gtk3/UI/Applet/GtkHostUIHandler.cs @@ -107,8 +107,6 @@ namespace Ryujinx.UI.Applet swkbdDialog.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax); swkbdDialog.SetInputValidation(args.KeyboardMode); - ((MainWindow)_parent).RendererWidget.NpadManager.BlockInputUpdates(); - if (swkbdDialog.Run() == (int)ResponseType.Ok) { inputText = swkbdDialog.InputEntry.Text; @@ -130,7 +128,6 @@ namespace Ryujinx.UI.Applet }); dialogCloseEvent.WaitOne(); - ((MainWindow)_parent).RendererWidget.NpadManager.UnblockInputUpdates(); userText = error ? null : inputText; diff --git a/src/Ryujinx.Input/HLE/NpadManager.cs b/src/Ryujinx.Input/HLE/NpadManager.cs index 2409ecf22c..4c7bb8b7a4 100644 --- a/src/Ryujinx.Input/HLE/NpadManager.cs +++ b/src/Ryujinx.Input/HLE/NpadManager.cs @@ -174,11 +174,6 @@ namespace Ryujinx.Input.HLE { lock (_lock) { - foreach (InputConfig inputConfig in _inputConfig) - { - _controllers[(int)inputConfig.PlayerIndex].GamepadDriver.Clear(); - } - _blockInputUpdates = false; } } diff --git a/src/Ryujinx.Input/IGamepadDriver.cs b/src/Ryujinx.Input/IGamepadDriver.cs index ff4d36993c..67b01c26ca 100644 --- a/src/Ryujinx.Input/IGamepadDriver.cs +++ b/src/Ryujinx.Input/IGamepadDriver.cs @@ -33,11 +33,5 @@ namespace Ryujinx.Input /// The unique id of the gamepad /// An instance of associated to the gamepad id given or null if not found IGamepad GetGamepad(string id); - - /// - /// Flush the internal state of the driver. - /// - /// Does nothing by default. - void Clear() { } } } diff --git a/src/Ryujinx/Input/AvaloniaKeyboard.cs b/src/Ryujinx/Input/AvaloniaKeyboard.cs index ff88de79e4..fbaaaabab8 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboard.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboard.cs @@ -195,7 +195,7 @@ namespace Ryujinx.Ava.Input public void Clear() { - _driver?.Clear(); + _driver?.ResetKeys(); } public void Dispose() { } diff --git a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs index 9f87e821ad..e9e71b99bd 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs @@ -94,7 +94,7 @@ namespace Ryujinx.Ava.Input return _pressedKeys.Contains(nativeKey); } - public void Clear() + public void ResetKeys() { _pressedKeys.Clear(); } diff --git a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs index 4bcf8eb949..4bcc35a7a5 100644 --- a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs +++ b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs @@ -122,7 +122,6 @@ namespace Ryujinx.Ava.UI.Applet { try { - _parent.ViewModel.AppHost.NpadManager.BlockInputUpdates(); var response = await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.SoftwareKeyboard], args); if (response.Result == UserResult.Ok) @@ -144,7 +143,6 @@ namespace Ryujinx.Ava.UI.Applet }); dialogCloseEvent.WaitOne(); - _parent.ViewModel.AppHost.NpadManager.UnblockInputUpdates(); userText = error ? null : inputText; From 3aea19460635ef9d1652a220e6806130dd6a7355 Mon Sep 17 00:00:00 2001 From: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> Date: Wed, 15 May 2024 06:06:58 -0400 Subject: [PATCH 86/87] Add Enhancement label to Feature Requests (#6804) --- .github/ISSUE_TEMPLATE/feature_request.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 383bbb1516..399aa039c5 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,6 +1,7 @@ name: Feature Request description: Suggest a new feature for Ryujinx. title: "[Feature Request]" +labels: enhancement body: - type: textarea id: overview From 091230af222a309584ab35df8379ab3a725cd3c1 Mon Sep 17 00:00:00 2001 From: SamusAranX Date: Wed, 15 May 2024 18:10:47 +0200 Subject: [PATCH 87/87] Improves some log messages and fixes a typo (#6773) * Improves some log messages and fixes a typo * oops * Update src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs Co-authored-by: Ac_K * Log config file path --------- Co-authored-by: Ac_K Co-authored-by: TSR Berry <20988865+TSRBerry@users.noreply.github.com> --- src/Ryujinx.Gtk3/Program.cs | 7 +++++-- src/Ryujinx.UI.Common/App/ApplicationData.cs | 2 +- src/Ryujinx.UI.Common/App/ApplicationLibrary.cs | 2 +- src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs | 5 ++++- src/Ryujinx/Program.cs | 7 +++++-- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/Ryujinx.Gtk3/Program.cs b/src/Ryujinx.Gtk3/Program.cs index f699343135..749cb69786 100644 --- a/src/Ryujinx.Gtk3/Program.cs +++ b/src/Ryujinx.Gtk3/Program.cs @@ -181,21 +181,24 @@ namespace Ryujinx { // No configuration, we load the default values and save it to disk ConfigurationPath = appDataConfigurationPath; + Logger.Notice.Print(LogClass.Application, $"No configuration file found. Saving default configuration to: {ConfigurationPath}"); ConfigurationState.Instance.LoadDefault(); ConfigurationState.Instance.ToFileFormat().SaveConfig(ConfigurationPath); } else { + Logger.Notice.Print(LogClass.Application, $"Loading configuration from: {ConfigurationPath}"); + if (ConfigurationFileFormat.TryLoad(ConfigurationPath, out ConfigurationFileFormat configurationFileFormat)) { ConfigurationState.Instance.Load(configurationFileFormat, ConfigurationPath); } else { - ConfigurationState.Instance.LoadDefault(); + Logger.Warning?.PrintMsg(LogClass.Application, $"Failed to load config! Loading the default config instead.\nFailed config location: {ConfigurationPath}"); - Logger.Warning?.PrintMsg(LogClass.Application, $"Failed to load config! Loading the default config instead.\nFailed config location {ConfigurationPath}"); + ConfigurationState.Instance.LoadDefault(); } } diff --git a/src/Ryujinx.UI.Common/App/ApplicationData.cs b/src/Ryujinx.UI.Common/App/ApplicationData.cs index 8cc7238e90..13c05655b6 100644 --- a/src/Ryujinx.UI.Common/App/ApplicationData.cs +++ b/src/Ryujinx.UI.Common/App/ApplicationData.cs @@ -45,7 +45,7 @@ namespace Ryujinx.UI.App.Common if (!System.IO.Path.Exists(titleFilePath)) { - Logger.Error?.Print(LogClass.Application, $"File does not exists. {titleFilePath}"); + Logger.Error?.Print(LogClass.Application, $"File \"{titleFilePath}\" does not exist."); return string.Empty; } diff --git a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs index 65cf7a9e6d..82783e638a 100644 --- a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs +++ b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs @@ -106,7 +106,7 @@ namespace Ryujinx.UI.App.Common if (!Directory.Exists(appDir)) { - Logger.Warning?.Print(LogClass.Application, $"The \"game_dirs\" section in \"{ReleaseInformation.ConfigName}\" contains an invalid directory: \"{appDir}\""); + Logger.Warning?.Print(LogClass.Application, $"The specified game directory \"{appDir}\" does not exist."); continue; } diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs index 01c60a5e23..8420dc5d98 100644 --- a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs @@ -11,6 +11,7 @@ using Ryujinx.UI.Common.Configuration.UI; using Ryujinx.UI.Common.Helper; using System; using System.Collections.Generic; +using System.Globalization; using System.Text.Json.Nodes; namespace Ryujinx.UI.Common.Configuration @@ -1594,7 +1595,9 @@ namespace Ryujinx.UI.Common.Configuration private static void LogValueChange(ReactiveEventArgs eventArgs, string valueName) { - Ryujinx.Common.Logging.Logger.Info?.Print(LogClass.Configuration, $"{valueName} set to: {eventArgs.NewValue}"); + string message = string.Create(CultureInfo.InvariantCulture, $"{valueName} set to: {eventArgs.NewValue}"); + + Ryujinx.Common.Logging.Logger.Info?.Print(LogClass.Configuration, message); } public static void Initialize() diff --git a/src/Ryujinx/Program.cs b/src/Ryujinx/Program.cs index f925ce154f..4f68ca24f0 100644 --- a/src/Ryujinx/Program.cs +++ b/src/Ryujinx/Program.cs @@ -148,21 +148,24 @@ namespace Ryujinx.Ava { // No configuration, we load the default values and save it to disk ConfigurationPath = appDataConfigurationPath; + Logger.Notice.Print(LogClass.Application, $"No configuration file found. Saving default configuration to: {ConfigurationPath}"); ConfigurationState.Instance.LoadDefault(); ConfigurationState.Instance.ToFileFormat().SaveConfig(ConfigurationPath); } else { + Logger.Notice.Print(LogClass.Application, $"Loading configuration from: {ConfigurationPath}"); + if (ConfigurationFileFormat.TryLoad(ConfigurationPath, out ConfigurationFileFormat configurationFileFormat)) { ConfigurationState.Instance.Load(configurationFileFormat, ConfigurationPath); } else { - ConfigurationState.Instance.LoadDefault(); + Logger.Warning?.PrintMsg(LogClass.Application, $"Failed to load config! Loading the default config instead.\nFailed config location: {ConfigurationPath}"); - Logger.Warning?.PrintMsg(LogClass.Application, $"Failed to load config! Loading the default config instead.\nFailed config location {ConfigurationPath}"); + ConfigurationState.Instance.LoadDefault(); } }