Compare commits

..

9 Commits

Author SHA1 Message Date
Evan Husted fe9fe2a10f UI: Added option to check for updates in the background 2025-02-10 19:28:46 -06:00
Evan Husted 6ab899f621 misc: chore: [ci skip] Use explicit types & target-typed new 2025-02-10 16:44:26 -06:00
FluffyOMC faacec9801 JIT Cache Regions + HLE SoNoSigpipe BSD socket mapping (#615)
Instead of one big 2048MB JIT Cache that'd crash the emulator when maxed
out, we now have it where we add 256MB JIT Cache regions when needed,
helping reduce allocated memory where games don't use the JIT cache for
it, and helping bigger games that DO need JIT cache bigger than 2048MB!

![image](https://github.com/user-attachments/assets/ff17dc48-6028-4377-8c73-746ab21ab83b)
(SSBU goes past the 2048MB JIT Cache limit that would normally crash
Ryujinx ^)

Also I added a BSD socket that Baba is You's networking for downloading
custom levels uses.
2025-02-10 15:53:44 -06:00
Evan Husted 55fdb3f6b2 headless: Default to Vulkan 2025-02-10 15:45:09 -06:00
Evan Husted 1129ab0e8c misc: chore: Remove unused property in ApplicationData 2025-02-10 15:44:58 -06:00
Evan Husted b6b391b2cf misc: chore: [ci skip] Remove unused 'using' directives from solution 2025-02-10 14:34:12 -06:00
Evan Husted f3cf03495d misc: add the ability to ignore UI logs when using trace & debug log levels 2025-02-10 14:25:47 -06:00
Evan Husted 7bce8206d5 misc: chore: small cleanups 2025-02-10 13:59:25 -06:00
Evan Husted efa0cc7554 UI: Show issue labels of games in the tooltip on playability status 2025-02-09 21:15:38 -06:00
37 changed files with 565 additions and 241 deletions
+2
View File
@@ -7,6 +7,7 @@ namespace ARMeilleure.Memory
public const int DefaultGranularity = 65536; // Mapping granularity in Windows. public const int DefaultGranularity = 65536; // Mapping granularity in Windows.
public IJitMemoryBlock Block { get; } public IJitMemoryBlock Block { get; }
public IJitMemoryAllocator Allocator { get; }
public nint Pointer => Block.Pointer; public nint Pointer => Block.Pointer;
@@ -21,6 +22,7 @@ namespace ARMeilleure.Memory
granularity = DefaultGranularity; granularity = DefaultGranularity;
} }
Allocator = allocator;
Block = allocator.Reserve(maxSize); Block = allocator.Reserve(maxSize);
_maxSize = maxSize; _maxSize = maxSize;
_sizeGranularity = granularity; _sizeGranularity = granularity;
+77 -35
View File
@@ -2,6 +2,8 @@ using ARMeilleure.CodeGen;
using ARMeilleure.CodeGen.Unwinding; using ARMeilleure.CodeGen.Unwinding;
using ARMeilleure.Memory; using ARMeilleure.Memory;
using ARMeilleure.Native; using ARMeilleure.Native;
using Humanizer;
using Ryujinx.Common.Logging;
using Ryujinx.Memory; using Ryujinx.Memory;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -18,9 +20,8 @@ namespace ARMeilleure.Translation.Cache
private static readonly int _pageMask = _pageSize - 1; private static readonly int _pageMask = _pageSize - 1;
private const int CodeAlignment = 4; // Bytes. private const int CodeAlignment = 4; // Bytes.
private const int CacheSize = 2047 * 1024 * 1024; private const int CacheSize = 256 * 1024 * 1024;
private static ReservedRegion _jitRegion;
private static JitCacheInvalidation _jitCacheInvalidator; private static JitCacheInvalidation _jitCacheInvalidator;
private static CacheMemoryAllocator _cacheAllocator; private static CacheMemoryAllocator _cacheAllocator;
@@ -30,6 +31,9 @@ namespace ARMeilleure.Translation.Cache
private static readonly Lock _lock = new(); private static readonly Lock _lock = new();
private static bool _initialized; private static bool _initialized;
private static readonly List<ReservedRegion> _jitRegions = new();
private static int _activeRegionIndex = 0;
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
[LibraryImport("kernel32.dll", SetLastError = true)] [LibraryImport("kernel32.dll", SetLastError = true)]
public static partial nint FlushInstructionCache(nint hProcess, nint lpAddress, nuint dwSize); public static partial nint FlushInstructionCache(nint hProcess, nint lpAddress, nuint dwSize);
@@ -48,7 +52,9 @@ namespace ARMeilleure.Translation.Cache
return; return;
} }
_jitRegion = new ReservedRegion(allocator, CacheSize); ReservedRegion firstRegion = new(allocator, CacheSize);
_jitRegions.Add(firstRegion);
_activeRegionIndex = 0;
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS()) if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
{ {
@@ -59,7 +65,9 @@ namespace ARMeilleure.Translation.Cache
if (OperatingSystem.IsWindows()) if (OperatingSystem.IsWindows())
{ {
JitUnwindWindows.InstallFunctionTableHandler(_jitRegion.Pointer, CacheSize, _jitRegion.Pointer + Allocate(_pageSize)); JitUnwindWindows.InstallFunctionTableHandler(
firstRegion.Pointer, CacheSize, firstRegion.Pointer + Allocate(_pageSize)
);
} }
_initialized = true; _initialized = true;
@@ -75,8 +83,8 @@ namespace ARMeilleure.Translation.Cache
Debug.Assert(_initialized); Debug.Assert(_initialized);
int funcOffset = Allocate(code.Length); int funcOffset = Allocate(code.Length);
ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
nint funcPtr = _jitRegion.Pointer + funcOffset; nint funcPtr = targetRegion.Pointer + funcOffset;
if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64) if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{ {
@@ -90,9 +98,9 @@ namespace ARMeilleure.Translation.Cache
} }
else else
{ {
ReprotectAsWritable(funcOffset, code.Length); ReprotectAsWritable(targetRegion, funcOffset, code.Length);
Marshal.Copy(code, 0, funcPtr, code.Length); Marshal.Copy(code, 0, funcPtr, code.Length);
ReprotectAsExecutable(funcOffset, code.Length); ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64) if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{ {
@@ -116,52 +124,83 @@ namespace ARMeilleure.Translation.Cache
{ {
Debug.Assert(_initialized); Debug.Assert(_initialized);
int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64()); foreach (ReservedRegion region in _jitRegions)
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
{ {
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size)); if (pointer.ToInt64() < region.Pointer.ToInt64() ||
_cacheEntries.RemoveAt(entryIndex); pointer.ToInt64() >= (region.Pointer + CacheSize).ToInt64())
{
continue;
}
int funcOffset = (int)(pointer.ToInt64() - region.Pointer.ToInt64());
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
{
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
_cacheEntries.RemoveAt(entryIndex);
}
return;
} }
} }
} }
private static void ReprotectAsWritable(int offset, int size) private static void ReprotectAsWritable(ReservedRegion region, int offset, int size)
{ {
int endOffs = offset + size; int endOffs = offset + size;
int regionStart = offset & ~_pageMask; int regionStart = offset & ~_pageMask;
int regionEnd = (endOffs + _pageMask) & ~_pageMask; int regionEnd = (endOffs + _pageMask) & ~_pageMask;
_jitRegion.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart)); region.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
} }
private static void ReprotectAsExecutable(int offset, int size) private static void ReprotectAsExecutable(ReservedRegion region, int offset, int size)
{ {
int endOffs = offset + size; int endOffs = offset + size;
int regionStart = offset & ~_pageMask; int regionStart = offset & ~_pageMask;
int regionEnd = (endOffs + _pageMask) & ~_pageMask; int regionEnd = (endOffs + _pageMask) & ~_pageMask;
_jitRegion.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart)); region.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
} }
private static int Allocate(int codeSize) private static int Allocate(int codeSize)
{ {
codeSize = AlignCodeSize(codeSize); codeSize = AlignCodeSize(codeSize);
int allocOffset = _cacheAllocator.Allocate(codeSize); for (int i = _activeRegionIndex; i < _jitRegions.Count; i++)
if (allocOffset < 0)
{ {
throw new OutOfMemoryException("JIT Cache exhausted."); int allocOffset = _cacheAllocator.Allocate(codeSize);
if (allocOffset >= 0)
{
_jitRegions[i].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
_activeRegionIndex = i;
return allocOffset;
}
} }
_jitRegion.ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize); int exhaustedRegion = _activeRegionIndex;
var newRegion = new ReservedRegion(_jitRegions[0].Allocator, CacheSize);
_jitRegions.Add(newRegion);
_activeRegionIndex = _jitRegions.Count - 1;
int newRegionNumber = _activeRegionIndex;
return allocOffset; Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation).");
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
int allocOffsetNew = _cacheAllocator.Allocate(codeSize);
if (allocOffsetNew < 0)
{
throw new OutOfMemoryException("Failed to allocate in new Cache Region!");
}
newRegion.ExpandIfNeeded((ulong)allocOffsetNew + (ulong)codeSize);
return allocOffsetNew;
} }
private static int AlignCodeSize(int codeSize) private static int AlignCodeSize(int codeSize)
{ {
return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1); return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1);
@@ -185,18 +224,21 @@ namespace ARMeilleure.Translation.Cache
{ {
lock (_lock) lock (_lock)
{ {
int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default)); foreach (ReservedRegion _ in _jitRegions)
if (index < 0)
{ {
index = ~index - 1; int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default));
}
if (index >= 0) if (index < 0)
{ {
entry = _cacheEntries[index]; index = ~index - 1;
entryIndex = index; }
return true;
if (index >= 0)
{
entry = _cacheEntries[index];
entryIndex = index;
return true;
}
} }
} }
@@ -144,17 +144,15 @@ namespace ARMeilleure.Translation.PTC
public List<ulong> GetBlacklistedFunctions() public List<ulong> GetBlacklistedFunctions()
{ {
List<ulong> funcs = new List<ulong>(); List<ulong> funcs = [];
foreach (var profiledFunc in ProfiledFuncs) foreach ((ulong ptr, FuncProfile funcProfile) in ProfiledFuncs)
{ {
if (profiledFunc.Value.Blacklist) if (!funcProfile.Blacklist)
{ continue;
if (!funcs.Contains(profiledFunc.Key))
{ if (!funcs.Contains(ptr))
funcs.Add(profiledFunc.Key); funcs.Add(ptr);
}
}
} }
return funcs; return funcs;
+61 -33
View File
@@ -1,4 +1,6 @@
using ARMeilleure.Memory; using ARMeilleure.Memory;
using Humanizer;
using Ryujinx.Common.Logging;
using Ryujinx.Memory; using Ryujinx.Memory;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -15,9 +17,8 @@ namespace Ryujinx.Cpu.LightningJit.Cache
private static readonly int _pageMask = _pageSize - 1; private static readonly int _pageMask = _pageSize - 1;
private const int CodeAlignment = 4; // Bytes. private const int CodeAlignment = 4; // Bytes.
private const int CacheSize = 2047 * 1024 * 1024; private const int CacheSize = 256 * 1024 * 1024;
private static ReservedRegion _jitRegion;
private static JitCacheInvalidation _jitCacheInvalidator; private static JitCacheInvalidation _jitCacheInvalidator;
private static CacheMemoryAllocator _cacheAllocator; private static CacheMemoryAllocator _cacheAllocator;
@@ -26,6 +27,8 @@ namespace Ryujinx.Cpu.LightningJit.Cache
private static readonly Lock _lock = new(); private static readonly Lock _lock = new();
private static bool _initialized; private static bool _initialized;
private static readonly List<ReservedRegion> _jitRegions = new();
private static int _activeRegionIndex = 0;
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
[LibraryImport("kernel32.dll", SetLastError = true)] [LibraryImport("kernel32.dll", SetLastError = true)]
@@ -45,7 +48,9 @@ namespace Ryujinx.Cpu.LightningJit.Cache
return; return;
} }
_jitRegion = new ReservedRegion(allocator, CacheSize); ReservedRegion firstRegion = new(allocator, CacheSize);
_jitRegions.Add(firstRegion);
_activeRegionIndex = 0;
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS()) if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
{ {
@@ -65,8 +70,8 @@ namespace Ryujinx.Cpu.LightningJit.Cache
Debug.Assert(_initialized); Debug.Assert(_initialized);
int funcOffset = Allocate(code.Length); int funcOffset = Allocate(code.Length);
ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
nint funcPtr = _jitRegion.Pointer + funcOffset; nint funcPtr = targetRegion.Pointer + funcOffset;
if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64) if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{ {
@@ -80,18 +85,11 @@ namespace Ryujinx.Cpu.LightningJit.Cache
} }
else else
{ {
ReprotectAsWritable(funcOffset, code.Length); ReprotectAsWritable(targetRegion, funcOffset, code.Length);
code.CopyTo(new Span<byte>((void*)funcPtr, code.Length)); Marshal.Copy(code.ToArray(), 0, funcPtr, code.Length);
ReprotectAsExecutable(funcOffset, code.Length); ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64) _jitCacheInvalidator?.Invalidate(funcPtr, (ulong)code.Length);
{
FlushInstructionCache(Process.GetCurrentProcess().Handle, funcPtr, (nuint)code.Length);
}
else
{
_jitCacheInvalidator?.Invalidate(funcPtr, (ulong)code.Length);
}
} }
Add(funcOffset, code.Length); Add(funcOffset, code.Length);
@@ -106,50 +104,80 @@ namespace Ryujinx.Cpu.LightningJit.Cache
{ {
Debug.Assert(_initialized); Debug.Assert(_initialized);
int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64()); foreach (ReservedRegion region in _jitRegions)
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
{ {
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size)); if (pointer.ToInt64() < region.Pointer.ToInt64() ||
_cacheEntries.RemoveAt(entryIndex); pointer.ToInt64() >= (region.Pointer + CacheSize).ToInt64())
{
continue;
}
int funcOffset = (int)(pointer.ToInt64() - region.Pointer.ToInt64());
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
{
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
_cacheEntries.RemoveAt(entryIndex);
}
return;
} }
} }
} }
private static void ReprotectAsWritable(int offset, int size) private static void ReprotectAsWritable(ReservedRegion region, int offset, int size)
{ {
int endOffs = offset + size; int endOffs = offset + size;
int regionStart = offset & ~_pageMask; int regionStart = offset & ~_pageMask;
int regionEnd = (endOffs + _pageMask) & ~_pageMask; int regionEnd = (endOffs + _pageMask) & ~_pageMask;
_jitRegion.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart)); region.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
} }
private static void ReprotectAsExecutable(int offset, int size) private static void ReprotectAsExecutable(ReservedRegion region, int offset, int size)
{ {
int endOffs = offset + size; int endOffs = offset + size;
int regionStart = offset & ~_pageMask; int regionStart = offset & ~_pageMask;
int regionEnd = (endOffs + _pageMask) & ~_pageMask; int regionEnd = (endOffs + _pageMask) & ~_pageMask;
_jitRegion.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart)); region.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
} }
private static int Allocate(int codeSize) private static int Allocate(int codeSize)
{ {
codeSize = AlignCodeSize(codeSize); codeSize = AlignCodeSize(codeSize);
int allocOffset = _cacheAllocator.Allocate(codeSize); for (int i = _activeRegionIndex; i < _jitRegions.Count; i++)
if (allocOffset < 0)
{ {
throw new OutOfMemoryException("JIT Cache exhausted."); int allocOffset = _cacheAllocator.Allocate(codeSize);
if (allocOffset >= 0)
{
_jitRegions[i].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
_activeRegionIndex = i;
return allocOffset;
}
} }
_jitRegion.ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize); int exhaustedRegion = _activeRegionIndex;
ReservedRegion newRegion = new(_jitRegions[0].Allocator, CacheSize);
_jitRegions.Add(newRegion);
_activeRegionIndex = _jitRegions.Count - 1;
int newRegionNumber = _activeRegionIndex;
return allocOffset; Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation).");
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
int allocOffsetNew = _cacheAllocator.Allocate(codeSize);
if (allocOffsetNew < 0)
{
throw new OutOfMemoryException("Failed to allocate in new Cache Region!");
}
newRegion.ExpandIfNeeded((ulong)allocOffsetNew + (ulong)codeSize);
return allocOffsetNew;
} }
private static int AlignCodeSize(int codeSize) private static int AlignCodeSize(int codeSize)
@@ -12,7 +12,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache
{ {
private const int CodeAlignment = 4; // Bytes. private const int CodeAlignment = 4; // Bytes.
private const int SharedCacheSize = 2047 * 1024 * 1024; private const int SharedCacheSize = 2047 * 1024 * 1024;
private const int LocalCacheSize = 128 * 1024 * 1024; private const int LocalCacheSize = 256 * 1024 * 1024;
// How many calls to the same function we allow until we pad the shared cache to force the function to become available there // How many calls to the same function we allow until we pad the shared cache to force the function to become available there
// and allow the guest to take the fast path. // and allow the guest to take the fast path.
-1
View File
@@ -1,5 +1,4 @@
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using System.Text.RegularExpressions;
namespace Ryujinx.Graphics.Vulkan namespace Ryujinx.Graphics.Vulkan
{ {
@@ -15,7 +15,6 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
namespace Ryujinx.HLE.HOS.Applets.Error namespace Ryujinx.HLE.HOS.Applets.Error
{ {
@@ -150,6 +150,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
{ BsdSocketOption.SoLinger, SocketOptionName.Linger }, { BsdSocketOption.SoLinger, SocketOptionName.Linger },
{ BsdSocketOption.SoOobInline, SocketOptionName.OutOfBandInline }, { BsdSocketOption.SoOobInline, SocketOptionName.OutOfBandInline },
{ BsdSocketOption.SoReusePort, SocketOptionName.ReuseAddress }, { BsdSocketOption.SoReusePort, SocketOptionName.ReuseAddress },
{ BsdSocketOption.SoNoSigpipe, SocketOptionName.DontLinger },
{ BsdSocketOption.SoSndBuf, SocketOptionName.SendBuffer }, { BsdSocketOption.SoSndBuf, SocketOptionName.SendBuffer },
{ BsdSocketOption.SoRcvBuf, SocketOptionName.ReceiveBuffer }, { BsdSocketOption.SoRcvBuf, SocketOptionName.ReceiveBuffer },
{ BsdSocketOption.SoSndLoWat, SocketOptionName.SendLowWater }, { BsdSocketOption.SoSndLoWat, SocketOptionName.SendLowWater },
-1
View File
@@ -1,4 +1,3 @@
using MsgPack;
using Ryujinx.Horizon.Common; using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Prepo.Types; using Ryujinx.Horizon.Prepo.Types;
using Ryujinx.Memory; using Ryujinx.Memory;
+184 -59
View File
@@ -76,7 +76,7 @@
"ID": "MenuBarFileOpenAppletOpenMiiApplet", "ID": "MenuBarFileOpenAppletOpenMiiApplet",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Mii-Bearbeitungsapplet", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Mii Edit Applet", "en_US": "Mii Edit Applet",
"es_ES": "Applet Editor Mii", "es_ES": "Applet Editor Mii",
@@ -176,7 +176,7 @@
"ID": "SettingsTabSystemMemoryManagerModeSoftware", "ID": "SettingsTabSystemMemoryManagerModeSoftware",
"Translations": { "Translations": {
"ar_SA": "البرنامج", "ar_SA": "البرنامج",
"de_DE": "Programme", "de_DE": "",
"el_GR": "Λογισμικό", "el_GR": "Λογισμικό",
"en_US": "Software", "en_US": "Software",
"es_ES": "", "es_ES": "",
@@ -326,7 +326,7 @@
"ID": "MenuBarFileOpenFromFileError", "ID": "MenuBarFileOpenFromFileError",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Keine Anwendungen im ausgewählten Datei gefunden.", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "No applications found in selected file.", "en_US": "No applications found in selected file.",
"es_ES": "No se encontraron aplicaciones en el archivo seleccionado.", "es_ES": "No se encontraron aplicaciones en el archivo seleccionado.",
@@ -376,7 +376,7 @@
"ID": "MenuBarFileLoadDlcFromFolder", "ID": "MenuBarFileLoadDlcFromFolder",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "DLC aus Ordner laden", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Load DLC From Folder", "en_US": "Load DLC From Folder",
"es_ES": "Cargar DLC Desde Carpeta", "es_ES": "Cargar DLC Desde Carpeta",
@@ -401,7 +401,7 @@
"ID": "MenuBarFileLoadTitleUpdatesFromFolder", "ID": "MenuBarFileLoadTitleUpdatesFromFolder",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Titel-Updates aus Ordner laden", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Load Title Updates From Folder", "en_US": "Load Title Updates From Folder",
"es_ES": "Cargar Actualizaciones de Títulos Desde Carpeta", "es_ES": "Cargar Actualizaciones de Títulos Desde Carpeta",
@@ -576,7 +576,7 @@
"ID": "MenuBarOptionsStartGamesWithoutUI", "ID": "MenuBarOptionsStartGamesWithoutUI",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Spiele ohne Benutzeroberfläche starten", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Start Games with UI Hidden", "en_US": "Start Games with UI Hidden",
"es_ES": "", "es_ES": "",
@@ -751,7 +751,7 @@
"ID": "MenuBarActionsScanAmiiboBin", "ID": "MenuBarActionsScanAmiiboBin",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Amiibo scannen (aus Bin-Datei)", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Scan An Amiibo (From Bin)", "en_US": "Scan An Amiibo (From Bin)",
"es_ES": "", "es_ES": "",
@@ -851,7 +851,7 @@
"ID": "MenuBarActionsInstallKeys", "ID": "MenuBarActionsInstallKeys",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Schlüssel installieren", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Install Keys", "en_US": "Install Keys",
"es_ES": "", "es_ES": "",
@@ -876,7 +876,7 @@
"ID": "MenuBarFileActionsInstallKeysFromFile", "ID": "MenuBarFileActionsInstallKeysFromFile",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Schlüssel aus KEYS oder ZIP installieren", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Install keys from KEYS or ZIP", "en_US": "Install keys from KEYS or ZIP",
"es_ES": "Instalar keys de KEYS o ZIP", "es_ES": "Instalar keys de KEYS o ZIP",
@@ -901,7 +901,7 @@
"ID": "MenuBarFileActionsInstallKeysFromFolder", "ID": "MenuBarFileActionsInstallKeysFromFolder",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Schlüssel aus einem Verzeichnis installieren", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Install keys from a directory", "en_US": "Install keys from a directory",
"es_ES": "Instalar keys de un directorio", "es_ES": "Instalar keys de un directorio",
@@ -1001,7 +1001,7 @@
"ID": "MenuBarActionsXCITrimmer", "ID": "MenuBarActionsXCITrimmer",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "XCI-Dateien trimmen", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Trim XCI Files", "en_US": "Trim XCI Files",
"es_ES": "Recortar archivos XCI", "es_ES": "Recortar archivos XCI",
@@ -1226,7 +1226,7 @@
"ID": "MenuBarHelpFaqAndGuides", "ID": "MenuBarHelpFaqAndGuides",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "FAQ & Anleitungen", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "FAQ & Guides", "en_US": "FAQ & Guides",
"es_ES": "", "es_ES": "",
@@ -1251,7 +1251,7 @@
"ID": "MenuBarHelpFaq", "ID": "MenuBarHelpFaq",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "FAQ & Fehlerbehebung Seite", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "FAQ & Troubleshooting Page", "en_US": "FAQ & Troubleshooting Page",
"es_ES": "", "es_ES": "",
@@ -1276,7 +1276,7 @@
"ID": "MenuBarHelpFaqTooltip", "ID": "MenuBarHelpFaqTooltip",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Öffnet die FAQ- und Fehlerbehebungsseite im offiziellen Ryujinx-Wiki", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Opens the FAQ and Troubleshooting page on the official Ryujinx wiki", "en_US": "Opens the FAQ and Troubleshooting page on the official Ryujinx wiki",
"es_ES": "", "es_ES": "",
@@ -1301,7 +1301,7 @@
"ID": "MenuBarHelpSetup", "ID": "MenuBarHelpSetup",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Setup- und Konfigurationsanleitung", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Setup & Configuration Guide", "en_US": "Setup & Configuration Guide",
"es_ES": "", "es_ES": "",
@@ -1326,7 +1326,7 @@
"ID": "MenuBarHelpSetupTooltip", "ID": "MenuBarHelpSetupTooltip",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Öffnet die Setup- und Konfigurationsanleitung im offiziellen Ryujinx-Wiki", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Opens the Setup & Configuration guide on the official Ryujinx wiki", "en_US": "Opens the Setup & Configuration guide on the official Ryujinx wiki",
"es_ES": "", "es_ES": "",
@@ -1351,7 +1351,7 @@
"ID": "MenuBarHelpMultiplayer", "ID": "MenuBarHelpMultiplayer",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Multiplayer (LDN/LAN) Anleitung", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Multiplayer (LDN/LAN) Guide", "en_US": "Multiplayer (LDN/LAN) Guide",
"es_ES": "", "es_ES": "",
@@ -1376,7 +1376,7 @@
"ID": "MenuBarHelpMultiplayerTooltip", "ID": "MenuBarHelpMultiplayerTooltip",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Öffnet die Multiplayer-Anleitung im offiziellen Ryujinx-Wiki", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Opens the Multiplayer guide on the official Ryujinx wiki", "en_US": "Opens the Multiplayer guide on the official Ryujinx wiki",
"es_ES": "", "es_ES": "",
@@ -1526,7 +1526,7 @@
"ID": "GameListHeaderDeveloper", "ID": "GameListHeaderDeveloper",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Entwickelt von {0}", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Developed by {0}", "en_US": "Developed by {0}",
"es_ES": "", "es_ES": "",
@@ -1826,7 +1826,7 @@
"ID": "GameListHeaderCompatibilityStatus", "ID": "GameListHeaderCompatibilityStatus",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Kompatibilität:", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Compatibility:", "en_US": "Compatibility:",
"es_ES": "", "es_ES": "",
@@ -3350,26 +3350,101 @@
{ {
"ID": "SettingsTabGeneralCheckUpdatesOnLaunch", "ID": "SettingsTabGeneralCheckUpdatesOnLaunch",
"Translations": { "Translations": {
"ar_SA": "التحقق من وجود تحديثات عند التشغيل", "ar_SA": "",
"de_DE": "Beim Start nach Updates suchen", "de_DE": "",
"el_GR": "Έλεγχος για Ενημερώσεις στην Εκκίνηση", "el_GR": "",
"en_US": "Check for Updates on Launch", "en_US": "Check for Updates:",
"es_ES": "Buscar actualizaciones al iniciar", "es_ES": "",
"fr_FR": "Vérifier les mises à jour au démarrage", "fr_FR": "",
"he_IL": "בדוק אם קיימים עדכונים בהפעלה", "he_IL": "",
"it_IT": "Controlla aggiornamenti all'avvio", "it_IT": "",
"ja_JP": "起動時にアップデートを確認する", "ja_JP": "",
"ko_KR": "시작 시, 업데이트 확인", "ko_KR": "",
"no_NO": "Se etter oppdateringer ved oppstart", "no_NO": "",
"pl_PL": "Sprawdzaj aktualizacje przy uruchomieniu", "pl_PL": "",
"pt_BR": "Verificar se há atualizações ao iniciar", "pt_BR": "",
"ru_RU": "Проверять наличие обновлений при запуске", "ru_RU": "",
"sv_SE": "Leta efter uppdatering vid uppstart", "sv_SE": "",
"th_TH": "ตรวจหาการอัปเดตเมื่อเปิดโปรแกรม", "th_TH": "",
"tr_TR": "Her Açılışta Güncellemeleri Denetle", "tr_TR": "",
"uk_UA": "Перевіряти наявність оновлень під час запуску", "uk_UA": "",
"zh_CN": "启动时检查更新", "zh_CN": "",
"zh_TW": "啟動時檢查更新" "zh_TW": ""
}
},
{
"ID": "SettingsTabGeneralCheckUpdatesOnLaunchOff",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Off",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SettingsTabGeneralCheckUpdatesOnLaunchPromptAtStartup",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Prompt",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SettingsTabGeneralCheckUpdatesOnLaunchBackground",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Background",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
} }
}, },
{ {
@@ -5747,6 +5822,31 @@
"zh_TW": "啟用客體日誌" "zh_TW": "啟用客體日誌"
} }
}, },
{
"ID": "SettingsTabLoggingEnableAvaloniaLogs",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Enable UI Logs",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{ {
"ID": "SettingsTabLoggingEnableFsAccessLogs", "ID": "SettingsTabLoggingEnableFsAccessLogs",
"Translations": { "Translations": {
@@ -16722,6 +16822,31 @@
"zh_TW": "謹慎使用" "zh_TW": "謹慎使用"
} }
}, },
{
"ID": "AvaloniaLogTooltip",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Prints Avalonia (UI) log messages in the console.",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{ {
"ID": "OpenGlLogLevel", "ID": "OpenGlLogLevel",
"Translations": { "Translations": {
@@ -23026,7 +23151,7 @@
"ID": "SettingsTabSystemVSyncModeTooltip", "ID": "SettingsTabSystemVSyncModeTooltip",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Emulierte vertikale Synchronisation. \"Switch\" emuliert die 60Hz-Bildwiederholfrequenz der Switch. \"Unbounded\" ist eine unbegrenzte Bildwiederholfrequenz.", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Emulated Vertical Sync. 'Switch' emulates the Switch's refresh rate of 60Hz. 'Unbounded' is an unbounded refresh rate.", "en_US": "Emulated Vertical Sync. 'Switch' emulates the Switch's refresh rate of 60Hz. 'Unbounded' is an unbounded refresh rate.",
"es_ES": "", "es_ES": "",
@@ -23051,7 +23176,7 @@
"ID": "SettingsTabSystemVSyncModeTooltipCustom", "ID": "SettingsTabSystemVSyncModeTooltipCustom",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Emulierte vertikale Synchronisation. \"Switch\" emuliert die 60Hz-Bildwiederholfrequenz der Switch. „Unbounded“ ist eine unbegrenzte Bildwiederholfrequenz. „Benutzerdefinierte Bildwiederholfrequenz“ emuliert die angegebene benutzerdefinierte Bildwiederholfrequenz.", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Emulated Vertical Sync. 'Switch' emulates the Switch's refresh rate of 60Hz. 'Unbounded' is an unbounded refresh rate. 'Custom Refresh Rate' emulates the specified custom refresh rate.", "en_US": "Emulated Vertical Sync. 'Switch' emulates the Switch's refresh rate of 60Hz. 'Unbounded' is an unbounded refresh rate. 'Custom Refresh Rate' emulates the specified custom refresh rate.",
"es_ES": "", "es_ES": "",
@@ -23076,7 +23201,7 @@
"ID": "SettingsTabSystemEnableCustomVSyncIntervalTooltip", "ID": "SettingsTabSystemEnableCustomVSyncIntervalTooltip",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Ermöglicht es dem Benutzer, eine emulierte Bildwiederholfrequenz festzulegen. In einigen Titeln kann dies die Geschwindigkeit der Spiel-Logik erhöhen oder verringern. In anderen Titeln kann dies dazu führen, dass die FPS auf ein Vielfaches der Bildwiederholfrequenz begrenzt werden oder zu unvorhersehbarem Verhalten führen. Dies ist eine experimentelle Funktion, ohne Garantien dafür, wie sich das Gameplay auswirkt. \n\nLassen Sie diese Option deaktiviert, wenn Sie sich nicht sicher sind.", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Allows the user to specify an emulated refresh rate. In some titles, this may speed up or slow down the rate of gameplay logic. In other titles, it may allow for capping FPS at some multiple of the refresh rate, or lead to unpredictable behavior. This is an experimental feature, with no guarantees for how gameplay will be affected. \n\nLeave OFF if unsure.", "en_US": "Allows the user to specify an emulated refresh rate. In some titles, this may speed up or slow down the rate of gameplay logic. In other titles, it may allow for capping FPS at some multiple of the refresh rate, or lead to unpredictable behavior. This is an experimental feature, with no guarantees for how gameplay will be affected. \n\nLeave OFF if unsure.",
"es_ES": "", "es_ES": "",
@@ -23101,7 +23226,7 @@
"ID": "SettingsTabSystemCustomVSyncIntervalValueTooltip", "ID": "SettingsTabSystemCustomVSyncIntervalValueTooltip",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Der Zielwert für die benutzerdefinierte Bildwiederholfrequenz.", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "The custom refresh rate target value.", "en_US": "The custom refresh rate target value.",
"es_ES": "", "es_ES": "",
@@ -23126,7 +23251,7 @@
"ID": "SettingsTabSystemCustomVSyncIntervalSliderTooltip", "ID": "SettingsTabSystemCustomVSyncIntervalSliderTooltip",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Die benutzerdefinierte Bildwiederholfrequenz als Prozentsatz der normalen Switch-Bildwiederholfrequenz.", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "The custom refresh rate, as a percentage of the normal Switch refresh rate.", "en_US": "The custom refresh rate, as a percentage of the normal Switch refresh rate.",
"es_ES": "", "es_ES": "",
@@ -23151,7 +23276,7 @@
"ID": "SettingsTabSystemCustomVSyncIntervalPercentage", "ID": "SettingsTabSystemCustomVSyncIntervalPercentage",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Benutzerdefinierte Bildwiederholfrequenz %:", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Custom Refresh Rate %:", "en_US": "Custom Refresh Rate %:",
"es_ES": "", "es_ES": "",
@@ -23176,7 +23301,7 @@
"ID": "SettingsTabSystemCustomVSyncIntervalValue", "ID": "SettingsTabSystemCustomVSyncIntervalValue",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Wert für benutzerdefinierte Bildwiederholfrequenz:", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Custom Refresh Rate Value:", "en_US": "Custom Refresh Rate Value:",
"es_ES": "", "es_ES": "",
@@ -23226,7 +23351,7 @@
"ID": "SettingsTabHotkeysToggleVSyncModeHotkey", "ID": "SettingsTabHotkeysToggleVSyncModeHotkey",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "VSync-Modus umschalten:", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Toggle VSync mode:", "en_US": "Toggle VSync mode:",
"es_ES": "", "es_ES": "",
@@ -23251,7 +23376,7 @@
"ID": "SettingsTabHotkeysIncrementCustomVSyncIntervalHotkey", "ID": "SettingsTabHotkeysIncrementCustomVSyncIntervalHotkey",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Benutzerdefinierte Bildwiederholfrequenz erhöhen:", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Raise custom refresh rate", "en_US": "Raise custom refresh rate",
"es_ES": "", "es_ES": "",
@@ -23276,7 +23401,7 @@
"ID": "SettingsTabHotkeysDecrementCustomVSyncIntervalHotkey", "ID": "SettingsTabHotkeysDecrementCustomVSyncIntervalHotkey",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Benutzerdefinierte Bildwiederholfrequenz senken:", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Lower custom refresh rate:", "en_US": "Lower custom refresh rate:",
"es_ES": "", "es_ES": "",
@@ -23301,7 +23426,7 @@
"ID": "CompatibilityListLastUpdated", "ID": "CompatibilityListLastUpdated",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Zuletzt aktualisiert: {0}", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Last updated: {0}", "en_US": "Last updated: {0}",
"es_ES": "", "es_ES": "",
@@ -23326,7 +23451,7 @@
"ID": "CompatibilityListWarning", "ID": "CompatibilityListWarning",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Diese Kompatibilitätsliste könnte veraltete Einträge enthalten. Teste dennoch Spiele im \"Ingame\"-Status.", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "This compatibility list might contain out of date entries.\nDo not be opposed to testing games in the \"Ingame\" status.", "en_US": "This compatibility list might contain out of date entries.\nDo not be opposed to testing games in the \"Ingame\" status.",
"es_ES": "", "es_ES": "",
@@ -23351,7 +23476,7 @@
"ID": "CompatibilityListSearchBoxWatermark", "ID": "CompatibilityListSearchBoxWatermark",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Kompatibilitätseinträge durchsuchen...", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Search compatibility entries...", "en_US": "Search compatibility entries...",
"es_ES": "", "es_ES": "",
@@ -23401,7 +23526,7 @@
"ID": "CompatibilityListOnlyShowOwnedGames", "ID": "CompatibilityListOnlyShowOwnedGames",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Nur eigene Spiele anzeigen", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Only show owned games", "en_US": "Only show owned games",
"es_ES": "", "es_ES": "",
@@ -23426,7 +23551,7 @@
"ID": "CompatibilityListPlayable", "ID": "CompatibilityListPlayable",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Spielbar", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Playable", "en_US": "Playable",
"es_ES": "", "es_ES": "",
@@ -23451,7 +23576,7 @@
"ID": "CompatibilityListIngame", "ID": "CompatibilityListIngame",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Im Spiel", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Ingame", "en_US": "Ingame",
"es_ES": "", "es_ES": "",
@@ -23626,7 +23751,7 @@
"ID": "CompatibilityListBootsTooltip", "ID": "CompatibilityListBootsTooltip",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Startet, kommt aber nicht über den Titelbildschirm hinaus.", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Boots but does not make it past the title screen.", "en_US": "Boots but does not make it past the title screen.",
"es_ES": "", "es_ES": "",
@@ -23651,7 +23776,7 @@
"ID": "CompatibilityListNothingTooltip", "ID": "CompatibilityListNothingTooltip",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Startet nicht oder zeigt keine Anzeichen von Aktivität.", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Does not boot or shows no signs of activity.", "en_US": "Does not boot or shows no signs of activity.",
"es_ES": "", "es_ES": "",
@@ -23676,7 +23801,7 @@
"ID": "ExtractAocListHeader", "ID": "ExtractAocListHeader",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "Wähle ein DLC zum Extrahieren aus", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Select a DLC to Extract", "en_US": "Select a DLC to Extract",
"es_ES": "", "es_ES": "",
-2
View File
@@ -1,6 +1,5 @@
using DiscordRPC; using DiscordRPC;
using Gommon; using Gommon;
using MsgPack;
using Ryujinx.Ava.Utilities; using Ryujinx.Ava.Utilities;
using Ryujinx.Ava.Utilities.AppLibrary; using Ryujinx.Ava.Utilities.AppLibrary;
using Ryujinx.Ava.Utilities.Configuration; using Ryujinx.Ava.Utilities.Configuration;
@@ -11,7 +10,6 @@ using Ryujinx.HLE;
using Ryujinx.HLE.Loaders.Processes; using Ryujinx.HLE.Loaders.Processes;
using Ryujinx.Horizon; using Ryujinx.Horizon;
using Ryujinx.Horizon.Prepo.Types; using Ryujinx.Horizon.Prepo.Types;
using System.Linq;
using System.Text; using System.Text;
namespace Ryujinx.Ava namespace Ryujinx.Ava
+1 -1
View File
@@ -387,7 +387,7 @@ namespace Ryujinx.Headless
[Option("graphics-shaders-dump-path", Required = false, HelpText = "Dumps shaders in this local directory. (Developer only)")] [Option("graphics-shaders-dump-path", Required = false, HelpText = "Dumps shaders in this local directory. (Developer only)")]
public string GraphicsShadersDumpPath { get; set; } public string GraphicsShadersDumpPath { get; set; }
[Option("graphics-backend", Required = false, Default = GraphicsBackend.OpenGl, HelpText = "Change Graphics Backend to use.")] [Option("graphics-backend", Required = false, Default = GraphicsBackend.Vulkan, HelpText = "Change Graphics Backend to use.")]
public GraphicsBackend GraphicsBackend { get; set; } public GraphicsBackend GraphicsBackend { get; set; }
[Option("preferred-gpu-vendor", Required = false, Default = "", HelpText = "When using the Vulkan backend, prefer using the GPU from the specified vendor.")] [Option("preferred-gpu-vendor", Required = false, Default = "", HelpText = "When using the Vulkan backend, prefer using the GPU from the specified vendor.")]
@@ -55,9 +55,21 @@
Tag="{Binding AppData.IdString}" Tag="{Binding AppData.IdString}"
Text="{Binding AppData.LocalizedStatus}" Text="{Binding AppData.LocalizedStatus}"
Foreground="{Binding AppData.PlayabilityStatus, Converter={x:Static helpers:PlayabilityStatusConverter.Shared}}" Foreground="{Binding AppData.PlayabilityStatus, Converter={x:Static helpers:PlayabilityStatusConverter.Shared}}"
ToolTip.Tip="{Binding AppData.LocalizedStatusTooltip}"
TextAlignment="Start" TextAlignment="Start"
TextWrapping="Wrap" /> TextWrapping="Wrap">
<ToolTip.Tip>
<StackPanel Orientation="Vertical">
<TextBlock
Text="{Binding AppData.LocalizedStatusTooltip}" />
<Separator
Margin="0, 10, 0, 10"
IsVisible="{Binding AppData.HasCompatibilityLabels}" />
<TextBlock
IsVisible="{Binding AppData.HasCompatibilityLabels}"
Text="{Binding AppData.FormattedCompatibilityLabels}" />
</StackPanel>
</ToolTip.Tip>
</TextBlock>
<Button.Styles> <Button.Styles>
<Style Selector="Button"> <Style Selector="Button">
<Setter Property="MinWidth" <Setter Property="MinWidth"
@@ -1,12 +1,9 @@
using Avalonia; using Avalonia.Controls;
using Avalonia.Controls;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Styling; using Avalonia.Styling;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using Ryujinx.Ava;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
@@ -93,8 +93,19 @@
IsVisible="{Binding HasPlayabilityInfo}" IsVisible="{Binding HasPlayabilityInfo}"
Background="{DynamicResource AppListBackgroundColor}" Background="{DynamicResource AppListBackgroundColor}"
Margin="-1, 0, 0, 0" Margin="-1, 0, 0, 0"
Padding="0" Padding="0">
ToolTip.Tip="{Binding LocalizedStatusTooltip}"> <ToolTip.Tip>
<StackPanel Orientation="Vertical">
<TextBlock
Text="{Binding LocalizedStatusTooltip}" />
<Separator
Margin="0, 10, 0, 10"
IsVisible="{Binding HasCompatibilityLabels}" />
<TextBlock
IsVisible="{Binding HasCompatibilityLabels}"
Text="{Binding FormattedCompatibilityLabels}" />
</StackPanel>
</ToolTip.Tip>
<TextBlock <TextBlock
Margin="1.5" Margin="1.5"
Tag="{Binding IdString}" Tag="{Binding IdString}"
@@ -7,7 +7,6 @@ using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.Utilities.AppLibrary; using Ryujinx.Ava.Utilities.AppLibrary;
using Ryujinx.Ava.Utilities.Compat; using Ryujinx.Ava.Utilities.Compat;
using System; using System;
using System.Globalization;
using System.Linq; using System.Linq;
namespace Ryujinx.Ava.UI.Controls namespace Ryujinx.Ava.UI.Controls
+7
View File
@@ -1,6 +1,7 @@
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.Utilities; using Avalonia.Utilities;
using Gommon; using Gommon;
using Ryujinx.Ava.Utilities.Configuration;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using System; using System;
using System.Text; using System.Text;
@@ -14,13 +15,19 @@ namespace Ryujinx.Ava.UI.Helpers
internal class LoggerAdapter : ILogSink internal class LoggerAdapter : ILogSink
{ {
private static bool _avaloniaLogsEnabled = ConfigurationState.Instance.Logger.EnableAvaloniaLog;
public static void Register() public static void Register()
{ {
AvaLogger.Sink = new LoggerAdapter(); AvaLogger.Sink = new LoggerAdapter();
ConfigurationState.Instance.Logger.EnableAvaloniaLog.Event
+= (_, e) => _avaloniaLogsEnabled = e.NewValue;
} }
private static RyuLogger.Log? GetLog(AvaLogLevel level, string area) private static RyuLogger.Log? GetLog(AvaLogLevel level, string area)
{ {
if (!_avaloniaLogsEnabled) return null;
return level switch return level switch
{ {
AvaLogLevel.Verbose => RyuLogger.Debug, AvaLogLevel.Verbose => RyuLogger.Debug,
@@ -1147,10 +1147,10 @@ namespace Ryujinx.Ava.UI.ViewModels
List<string> dirs = result.Select(it => it.Path.LocalPath).ToList(); List<string> dirs = result.Select(it => it.Path.LocalPath).ToList();
int numAdded = onDirsSelected(dirs, out int numRemoved); int numAdded = onDirsSelected(dirs, out int numRemoved);
string msg = String.Join("\r\n", new string[] { string msg = string.Join("\n",
string.Format(LocaleManager.Instance[localeMessageRemovedKey], numRemoved), string.Format(LocaleManager.Instance[localeMessageRemovedKey], numRemoved),
string.Format(LocaleManager.Instance[localeMessageAddedKey], numAdded) string.Format(LocaleManager.Instance[localeMessageAddedKey], numAdded)
}); );
await Dispatcher.UIThread.InvokeAsync(async () => await Dispatcher.UIThread.InvokeAsync(async () =>
{ {
@@ -13,6 +13,7 @@ using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
using Ryujinx.Ava.Utilities.Configuration; using Ryujinx.Ava.Utilities.Configuration;
using Ryujinx.Ava.Utilities.Configuration.System; using Ryujinx.Ava.Utilities.Configuration.System;
using Ryujinx.Ava.Utilities.Configuration.UI;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Multiplayer; using Ryujinx.Common.Configuration.Multiplayer;
using Ryujinx.Common.GraphicsDriver; using Ryujinx.Common.GraphicsDriver;
@@ -121,6 +122,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool RememberWindowState { get; set; } public bool RememberWindowState { get; set; }
public bool ShowTitleBar { get; set; } public bool ShowTitleBar { get; set; }
public int HideCursor { get; set; } public int HideCursor { get; set; }
public int UpdateCheckerType { get; set; }
public bool EnableDockedMode { get; set; } public bool EnableDockedMode { get; set; }
public bool EnableKeyboard { get; set; } public bool EnableKeyboard { get; set; }
public bool EnableMouse { get; set; } public bool EnableMouse { get; set; }
@@ -204,6 +206,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool EnableTrace { get; set; } public bool EnableTrace { get; set; }
public bool EnableGuest { get; set; } public bool EnableGuest { get; set; }
public bool EnableFsAccessLog { get; set; } public bool EnableFsAccessLog { get; set; }
public bool EnableAvaloniaLog { get; set; }
public bool EnableDebug { get; set; } public bool EnableDebug { get; set; }
public bool IsOpenAlEnabled { get; set; } public bool IsOpenAlEnabled { get; set; }
public bool IsSoundIoEnabled { get; set; } public bool IsSoundIoEnabled { get; set; }
@@ -475,6 +478,7 @@ namespace Ryujinx.Ava.UI.ViewModels
RememberWindowState = config.RememberWindowState; RememberWindowState = config.RememberWindowState;
ShowTitleBar = config.ShowTitleBar; ShowTitleBar = config.ShowTitleBar;
HideCursor = (int)config.HideCursor.Value; HideCursor = (int)config.HideCursor.Value;
UpdateCheckerType = (int)config.UpdateCheckerType.Value;
GameDirectories.Clear(); GameDirectories.Clear();
GameDirectories.AddRange(config.UI.GameDirs.Value); GameDirectories.AddRange(config.UI.GameDirs.Value);
@@ -560,6 +564,7 @@ namespace Ryujinx.Ava.UI.ViewModels
EnableGuest = config.Logger.EnableGuest; EnableGuest = config.Logger.EnableGuest;
EnableDebug = config.Logger.EnableDebug; EnableDebug = config.Logger.EnableDebug;
EnableFsAccessLog = config.Logger.EnableFsAccessLog; EnableFsAccessLog = config.Logger.EnableFsAccessLog;
EnableAvaloniaLog = config.Logger.EnableAvaloniaLog;
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode; FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value; OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
@@ -580,6 +585,7 @@ namespace Ryujinx.Ava.UI.ViewModels
config.RememberWindowState.Value = RememberWindowState; config.RememberWindowState.Value = RememberWindowState;
config.ShowTitleBar.Value = ShowTitleBar; config.ShowTitleBar.Value = ShowTitleBar;
config.HideCursor.Value = (HideCursorMode)HideCursor; config.HideCursor.Value = (HideCursorMode)HideCursor;
config.UpdateCheckerType.Value = (UpdaterType)UpdateCheckerType;
if (GameDirectoryChanged) if (GameDirectoryChanged)
{ {
@@ -679,6 +685,7 @@ namespace Ryujinx.Ava.UI.ViewModels
config.Logger.EnableGuest.Value = EnableGuest; config.Logger.EnableGuest.Value = EnableGuest;
config.Logger.EnableDebug.Value = EnableDebug; config.Logger.EnableDebug.Value = EnableDebug;
config.Logger.EnableFsAccessLog.Value = EnableFsAccessLog; config.Logger.EnableFsAccessLog.Value = EnableFsAccessLog;
config.Logger.EnableAvaloniaLog.Value = EnableAvaloniaLog;
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode; config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel; config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
@@ -74,6 +74,10 @@
ToolTip.Tip="{ext:Locale DebugLogTooltip}"> ToolTip.Tip="{ext:Locale DebugLogTooltip}">
<TextBlock Text="{ext:Locale SettingsTabLoggingEnableDebugLogs}" /> <TextBlock Text="{ext:Locale SettingsTabLoggingEnableDebugLogs}" />
</CheckBox> </CheckBox>
<CheckBox IsChecked="{Binding EnableAvaloniaLog}"
ToolTip.Tip="{ext:Locale AvaloniaLogTooltip}">
<TextBlock Text="{ext:Locale SettingsTabLoggingEnableAvaloniaLogs}" />
</CheckBox>
<StackPanel Margin="0,10,0,0" Orientation="Horizontal" VerticalAlignment="Stretch"> <StackPanel Margin="0,10,0,0" Orientation="Horizontal" VerticalAlignment="Stretch">
<TextBlock VerticalAlignment="Center" <TextBlock VerticalAlignment="Center"
ToolTip.Tip="{ext:Locale FSAccessLogModeTooltip}" ToolTip.Tip="{ext:Locale FSAccessLogModeTooltip}"
@@ -1,5 +1,4 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Interactivity;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone; using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
@@ -6,6 +6,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup" xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
xmlns:helper="clr-namespace:Ryujinx.Common.Helper;assembly=Ryujinx.Common"
mc:Ignorable="d" mc:Ignorable="d"
x:DataType="viewModels:SettingsViewModel"> x:DataType="viewModels:SettingsViewModel">
<Design.DataContext> <Design.DataContext>
@@ -30,18 +31,33 @@
ToolTip.Tip="{ext:Locale ToggleDiscordTooltip}" ToolTip.Tip="{ext:Locale ToggleDiscordTooltip}"
Text="{ext:Locale SettingsTabGeneralEnableDiscordRichPresence}" /> Text="{ext:Locale SettingsTabGeneralEnableDiscordRichPresence}" />
</CheckBox> </CheckBox>
<CheckBox IsChecked="{Binding CheckUpdatesOnStart}">
<TextBlock Text="{ext:Locale SettingsTabGeneralCheckUpdatesOnLaunch}" />
</CheckBox>
<CheckBox IsChecked="{Binding ShowConfirmExit}"> <CheckBox IsChecked="{Binding ShowConfirmExit}">
<TextBlock Text="{ext:Locale SettingsTabGeneralShowConfirmExitDialog}" /> <TextBlock Text="{ext:Locale SettingsTabGeneralShowConfirmExitDialog}" />
</CheckBox> </CheckBox>
<CheckBox IsChecked="{Binding RememberWindowState}"> <CheckBox IsChecked="{Binding RememberWindowState}">
<TextBlock Text="{ext:Locale SettingsTabGeneralRememberWindowState}" /> <TextBlock Text="{ext:Locale SettingsTabGeneralRememberWindowState}" />
</CheckBox> </CheckBox>
<CheckBox IsChecked="{Binding ShowTitleBar}" Name="ShowTitleBarBox"> <CheckBox IsChecked="{Binding ShowTitleBar}" IsVisible="{x:Static helper:RunningPlatform.IsWindows}">
<TextBlock Text="{ext:Locale SettingsTabGeneralShowTitleBar}" /> <TextBlock Text="{ext:Locale SettingsTabGeneralShowTitleBar}" />
</CheckBox> </CheckBox>
<StackPanel Margin="0, 15, 0, 0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{ext:Locale SettingsTabGeneralCheckUpdatesOnLaunch}"
Width="150" />
<ComboBox SelectedIndex="{Binding UpdateCheckerType}"
HorizontalContentAlignment="Left"
MinWidth="100">
<ComboBoxItem>
<TextBlock Text="{ext:Locale SettingsTabGeneralCheckUpdatesOnLaunchOff}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{ext:Locale SettingsTabGeneralCheckUpdatesOnLaunchPromptAtStartup}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{ext:Locale SettingsTabGeneralCheckUpdatesOnLaunchBackground}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
<StackPanel Margin="0, 15, 0, 0" Orientation="Horizontal"> <StackPanel Margin="0, 15, 0, 0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" <TextBlock VerticalAlignment="Center"
Text="{ext:Locale SettingsTabGeneralHideCursor}" Text="{ext:Locale SettingsTabGeneralHideCursor}"
@@ -21,7 +21,6 @@ namespace Ryujinx.Ava.UI.Views.Settings
public SettingsUiView() public SettingsUiView()
{ {
InitializeComponent(); InitializeComponent();
ShowTitleBarBox.IsVisible = OperatingSystem.IsWindows();
AddGameDirButton.Command = AddGameDirButton.Command =
Commands.Create(() => AddDirButton(GameDirPathBox, ViewModel.GameDirectories, true)); Commands.Create(() => AddDirButton(GameDirPathBox, ViewModel.GameDirectories, true));
AddAutoloadDirButton.Command = AddAutoloadDirButton.Command =
+23 -3
View File
@@ -19,6 +19,7 @@ using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.Utilities; using Ryujinx.Ava.Utilities;
using Ryujinx.Ava.Utilities.AppLibrary; using Ryujinx.Ava.Utilities.AppLibrary;
using Ryujinx.Ava.Utilities.Configuration; using Ryujinx.Ava.Utilities.Configuration;
using Ryujinx.Ava.Utilities.Configuration.UI;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Helper; using Ryujinx.Common.Helper;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
@@ -400,10 +401,29 @@ namespace Ryujinx.Ava.UI.Windows
await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys)); await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys));
} }
if (ConfigurationState.Instance.CheckUpdatesOnStart && !CommandLineState.HideAvailableUpdates && Updater.CanUpdate()) if (!Updater.CanUpdate() || CommandLineState.HideAvailableUpdates)
return;
switch (ConfigurationState.Instance.UpdateCheckerType.Value)
{ {
await Updater.BeginUpdateAsync() case UpdaterType.PromptAtStartup:
.Catch(task => Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}")); await Updater.BeginUpdateAsync()
.Catch(task => Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}"));
break;
case UpdaterType.CheckInBackground:
if ((await Updater.CheckVersionAsync()).TryGet(out (Version Current, Version Incoming) versions))
{
string newVersionString = ReleaseInformation.IsCanaryBuild
? $"Canary {versions.Current} -> Canary {versions.Incoming}"
: $"{versions.Current} -> {versions.Incoming}";
if (versions.Current < versions.Incoming)
NotificationHelper.ShowInformation(
title: "Update Available",
text: newVersionString,
onClick: () => _ = Updater.BeginUpdateAsync());
}
break;
} }
} }
+31 -28
View File
@@ -43,7 +43,18 @@ namespace Ryujinx.Ava
private const int ConnectionCount = 4; private const int ConnectionCount = 4;
private static string _buildVer; private static string _buildVer;
private static string _platformExt;
private static readonly string _platformExt =
RunningPlatform.IsMacOS
? "macos_universal.app.tar.gz"
: RunningPlatform.IsWindows
? "win_x64.zip"
: RunningPlatform.IsX64Linux
? "linux_x64.tar.gz"
: RunningPlatform.IsArmLinux
? "linux_arm64.tar.gz"
: throw new PlatformNotSupportedException();
private static string _buildUrl; private static string _buildUrl;
private static long _buildSize; private static long _buildSize;
private static bool _updateSuccessful; private static bool _updateSuccessful;
@@ -51,30 +62,8 @@ namespace Ryujinx.Ava
private static readonly string[] _windowsDependencyDirs = []; private static readonly string[] _windowsDependencyDirs = [];
public static async Task BeginUpdateAsync(bool showVersionUpToDate = false) public static async Task<Optional<(Version Current, Version Incoming)>> CheckVersionAsync(bool showVersionUpToDate = false)
{ {
if (_running)
{
return;
}
_running = true;
// Detect current platform
if (OperatingSystem.IsMacOS())
{
_platformExt = "macos_universal.app.tar.gz";
}
else if (OperatingSystem.IsWindows())
{
_platformExt = "win_x64.zip";
}
else if (OperatingSystem.IsLinux())
{
string arch = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "arm64" : "x64";
_platformExt = $"linux_{arch}.tar.gz";
}
if (!Version.TryParse(Program.Version, out Version currentVersion)) if (!Version.TryParse(Program.Version, out Version currentVersion))
{ {
Logger.Error?.Print(LogClass.Application, $"Failed to convert the current {RyujinxApp.FullAppName} version!"); Logger.Error?.Print(LogClass.Application, $"Failed to convert the current {RyujinxApp.FullAppName} version!");
@@ -85,7 +74,7 @@ namespace Ryujinx.Ava
_running = false; _running = false;
return; return default;
} }
Logger.Info?.Print(LogClass.Application, "Checking for updates."); Logger.Info?.Print(LogClass.Application, "Checking for updates.");
@@ -123,7 +112,7 @@ namespace Ryujinx.Ava
_running = false; _running = false;
return; return default;
} }
break; break;
@@ -149,7 +138,7 @@ namespace Ryujinx.Ava
_running = false; _running = false;
return; return default;
} }
} }
catch (Exception exception) catch (Exception exception)
@@ -161,7 +150,7 @@ namespace Ryujinx.Ava
_running = false; _running = false;
return; return default;
} }
if (!Version.TryParse(_buildVer, out Version newVersion)) if (!Version.TryParse(_buildVer, out Version newVersion))
@@ -174,9 +163,23 @@ namespace Ryujinx.Ava
_running = false; _running = false;
return (currentVersion, null);
}
return (currentVersion, newVersion);
}
public static async Task BeginUpdateAsync(bool showVersionUpToDate = false)
{
if (_running)
{
return; return;
} }
_running = true;
(Version currentVersion, Version newVersion) = (await CheckVersionAsync(showVersionUpToDate)).OrDefault();
if (newVersion <= currentVersion) if (newVersion <= currentVersion)
{ {
if (showVersionUpToDate) if (showVersionUpToDate)
@@ -1,3 +1,4 @@
using Gommon;
using LibHac.Common; using LibHac.Common;
using LibHac.Fs; using LibHac.Fs;
using LibHac.Fs.Fsa; using LibHac.Fs.Fsa;
@@ -14,6 +15,7 @@ using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.Loaders.Processes.Extensions; using Ryujinx.HLE.Loaders.Processes.Extensions;
using System; using System;
using System.IO; using System.IO;
using System.Text;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace Ryujinx.Ava.Utilities.AppLibrary namespace Ryujinx.Ava.Utilities.AppLibrary
@@ -32,33 +34,12 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
set set
{ {
_id = value; _id = value;
PlayabilityStatus = CompatibilityCsv.GetStatus(Id);
Compatibility = CompatibilityCsv.Find(Id);
} }
} }
public string Developer { get; set; } = "Unknown"; public string Developer { get; set; } = "Unknown";
public string Version { get; set; } = "0"; public string Version { get; set; } = "0";
public bool HasPlayabilityInfo => PlayabilityStatus != null;
public string LocalizedStatus =>
PlayabilityStatus.HasValue
? LocaleManager.Instance[PlayabilityStatus!.Value]
: string.Empty;
public LocaleKeys? PlayabilityStatus { get; set; }
public string LocalizedStatusTooltip =>
PlayabilityStatus.HasValue
#pragma warning disable CS8509 // It is exhaustive for any value this property can contain.
? LocaleManager.Instance[PlayabilityStatus!.Value switch
#pragma warning restore CS8509
{
LocaleKeys.CompatibilityListPlayable => LocaleKeys.CompatibilityListPlayableTooltip,
LocaleKeys.CompatibilityListIngame => LocaleKeys.CompatibilityListIngameTooltip,
LocaleKeys.CompatibilityListMenus => LocaleKeys.CompatibilityListMenusTooltip,
LocaleKeys.CompatibilityListBoots => LocaleKeys.CompatibilityListBootsTooltip,
LocaleKeys.CompatibilityListNothing => LocaleKeys.CompatibilityListNothingTooltip,
}]
: string.Empty;
public int PlayerCount { get; set; } public int PlayerCount { get; set; }
public int GameCount { get; set; } public int GameCount { get; set; }
@@ -78,11 +59,39 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
public string TimePlayedString => ValueFormatUtils.FormatTimeSpan(TimePlayed); public string TimePlayedString => ValueFormatUtils.FormatTimeSpan(TimePlayed);
public bool HasPlayedPreviously => TimePlayedString != string.Empty; public bool HasPlayedPreviously => TimePlayed.TotalSeconds > 1;
public string LastPlayedString => ValueFormatUtils.FormatDateTime(LastPlayed)?.Replace(" ", "\n"); public string LastPlayedString => ValueFormatUtils.FormatDateTime(LastPlayed)?.Replace(" ", "\n");
public string FileSizeString => ValueFormatUtils.FormatFileSize(FileSize); public string FileSizeString => ValueFormatUtils.FormatFileSize(FileSize);
public Optional<CompatibilityEntry> Compatibility { get; private set; }
public bool HasPlayabilityInfo => Compatibility.HasValue;
public string LocalizedStatus => Compatibility.Convert(x => x.LocalizedStatus);
public bool HasCompatibilityLabels => !FormattedCompatibilityLabels.Equals(string.Empty);
public string FormattedCompatibilityLabels
=> Compatibility.Convert(x => x.FormattedIssueLabels).OrElse(string.Empty);
public LocaleKeys? PlayabilityStatus => Compatibility.Convert(x => x.Status).OrElse(null);
public string LocalizedStatusTooltip =>
Compatibility.Convert(x =>
#pragma warning disable CS8509 It is exhaustive for all possible values this can contain.
LocaleManager.Instance[x.Status switch
#pragma warning restore CS8509
{
LocaleKeys.CompatibilityListPlayable => LocaleKeys.CompatibilityListPlayableTooltip,
LocaleKeys.CompatibilityListIngame => LocaleKeys.CompatibilityListIngameTooltip,
LocaleKeys.CompatibilityListMenus => LocaleKeys.CompatibilityListMenusTooltip,
LocaleKeys.CompatibilityListBoots => LocaleKeys.CompatibilityListBootsTooltip,
LocaleKeys.CompatibilityListNothing => LocaleKeys.CompatibilityListNothingTooltip,
}]
).OrElse(string.Empty);
[JsonIgnore] public string IdString => Id.ToString("x16"); [JsonIgnore] public string IdString => Id.ToString("x16");
@@ -92,16 +101,16 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
public static string GetBuildId(VirtualFileSystem virtualFileSystem, IntegrityCheckLevel checkLevel, string titleFilePath) public static string GetBuildId(VirtualFileSystem virtualFileSystem, IntegrityCheckLevel checkLevel, string titleFilePath)
{ {
using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read);
Nca mainNca = null;
Nca patchNca = null;
if (!System.IO.Path.Exists(titleFilePath)) if (!System.IO.Path.Exists(titleFilePath))
{ {
Logger.Error?.Print(LogClass.Application, $"File \"{titleFilePath}\" does not exist."); Logger.Error?.Print(LogClass.Application, $"File \"{titleFilePath}\" does not exist.");
return string.Empty; return string.Empty;
} }
using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read);
Nca mainNca = null;
Nca patchNca = null;
string extension = System.IO.Path.GetExtension(titleFilePath).ToLower(); string extension = System.IO.Path.GetExtension(titleFilePath).ToLower();
@@ -60,10 +60,21 @@ namespace Ryujinx.Ava.Utilities.Compat
} }
} }
public static CompatibilityEntry Find(string titleId)
=> Entries.FirstOrDefault(x => x.TitleId.HasValue && x.TitleId.Value.EqualsIgnoreCase(titleId));
public static CompatibilityEntry Find(ulong titleId)
=> Find(titleId.ToString("X16"));
public static LocaleKeys? GetStatus(string titleId) public static LocaleKeys? GetStatus(string titleId)
=> Entries.FirstOrDefault(x => x.TitleId.HasValue && x.TitleId.Value.EqualsIgnoreCase(titleId))?.Status; => Find(titleId)?.Status;
public static LocaleKeys? GetStatus(ulong titleId) => GetStatus(titleId.ToString("X16")); public static LocaleKeys? GetStatus(ulong titleId) => GetStatus(titleId.ToString("X16"));
public static string GetLabels(string titleId)
=> Find(titleId)?.FormattedIssueLabels;
public static string GetLabels(ulong titleId) => GetLabels(titleId.ToString("X16"));
} }
public class CompatibilityEntry public class CompatibilityEntry
@@ -15,7 +15,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// <summary> /// <summary>
/// The current version of the file format /// The current version of the file format
/// </summary> /// </summary>
public const int CurrentVersion = 63; public const int CurrentVersion = 65;
/// <summary> /// <summary>
/// Version of the configuration file format /// Version of the configuration file format
@@ -111,6 +111,11 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// Enables printing FS access log messages /// Enables printing FS access log messages
/// </summary> /// </summary>
public bool LoggingEnableFsAccessLog { get; set; } public bool LoggingEnableFsAccessLog { get; set; }
/// <summary>
/// Enables log messages from Avalonia
/// </summary>
public bool LoggingEnableAvalonia { get; set; }
/// <summary> /// <summary>
/// Controls which log messages are written to the log targets /// Controls which log messages are written to the log targets
@@ -158,9 +163,14 @@ namespace Ryujinx.Ava.Utilities.Configuration
public bool EnableDiscordIntegration { get; set; } public bool EnableDiscordIntegration { get; set; }
/// <summary> /// <summary>
/// Checks for updates when Ryujinx starts when enabled /// DEPRECATED: Checks for updates when Ryujinx starts when enabled
/// </summary> /// </summary>
public bool CheckUpdatesOnStart { get; set; } public bool CheckUpdatesOnStart { get; set; }
/// <summary>
/// Checks for updates when Ryujinx starts when enabled, either prompting when an update is found or just showing a notification.
/// </summary>
public UpdaterType UpdateCheckerType { get; set; }
/// <summary> /// <summary>
/// Show "Confirm Exit" Dialog /// Show "Confirm Exit" Dialog
@@ -45,6 +45,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
EnableDiscordIntegration.Value = cff.EnableDiscordIntegration; EnableDiscordIntegration.Value = cff.EnableDiscordIntegration;
CheckUpdatesOnStart.Value = cff.CheckUpdatesOnStart; CheckUpdatesOnStart.Value = cff.CheckUpdatesOnStart;
UpdateCheckerType.Value = cff.UpdateCheckerType;
ShowConfirmExit.Value = cff.ShowConfirmExit; ShowConfirmExit.Value = cff.ShowConfirmExit;
RememberWindowState.Value = cff.RememberWindowState; RememberWindowState.Value = cff.RememberWindowState;
ShowTitleBar.Value = cff.ShowTitleBar; ShowTitleBar.Value = cff.ShowTitleBar;
@@ -430,7 +431,9 @@ namespace Ryujinx.Ava.Utilities.Configuration
} }
}), }),
(62, static cff => cff.RainbowSpeed = 1f), (62, static cff => cff.RainbowSpeed = 1f),
(63, static cff => cff.MatchSystemTime = false) (63, static cff => cff.MatchSystemTime = false),
(64, static cff => cff.LoggingEnableAvalonia = false),
(65, static cff => cff.UpdateCheckerType = cff.CheckUpdatesOnStart ? UpdaterType.PromptAtStartup : UpdaterType.Off)
); );
} }
} }
@@ -1,6 +1,7 @@
using ARMeilleure; using ARMeilleure;
using Gommon; using Gommon;
using Ryujinx.Ava.Utilities.Configuration.System; using Ryujinx.Ava.Utilities.Configuration.System;
using Ryujinx.Ava.Utilities.Configuration.UI;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid;
@@ -254,6 +255,11 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// Enables printing FS access log messages /// Enables printing FS access log messages
/// </summary> /// </summary>
public ReactiveObject<bool> EnableFsAccessLog { get; private set; } public ReactiveObject<bool> EnableFsAccessLog { get; private set; }
/// <summary>
/// Enables log messages from Avalonia
/// </summary>
public ReactiveObject<bool> EnableAvaloniaLog { get; private set; }
/// <summary> /// <summary>
/// Controls which log messages are written to the log targets /// Controls which log messages are written to the log targets
@@ -281,6 +287,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
EnableTrace = new ReactiveObject<bool>(); EnableTrace = new ReactiveObject<bool>();
EnableGuest = new ReactiveObject<bool>(); EnableGuest = new ReactiveObject<bool>();
EnableFsAccessLog = new ReactiveObject<bool>(); EnableFsAccessLog = new ReactiveObject<bool>();
EnableAvaloniaLog = new ReactiveObject<bool>();
FilteredClasses = new ReactiveObject<LogClass[]>(); FilteredClasses = new ReactiveObject<LogClass[]>();
EnableFileLog = new ReactiveObject<bool>(); EnableFileLog = new ReactiveObject<bool>();
EnableFileLog.LogChangesToValue(nameof(EnableFileLog)); EnableFileLog.LogChangesToValue(nameof(EnableFileLog));
@@ -761,6 +768,11 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// Checks for updates when Ryujinx starts when enabled /// Checks for updates when Ryujinx starts when enabled
/// </summary> /// </summary>
public ReactiveObject<bool> CheckUpdatesOnStart { get; private set; } public ReactiveObject<bool> CheckUpdatesOnStart { get; private set; }
/// <summary>
/// Checks for updates when Ryujinx starts when enabled, either prompting when an update is found or just showing a notification.
/// </summary>
public ReactiveObject<UpdaterType> UpdateCheckerType { get; private set; }
/// <summary> /// <summary>
/// Show "Confirm Exit" Dialog /// Show "Confirm Exit" Dialog
@@ -798,6 +810,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
Hacks = new HacksSection(); Hacks = new HacksSection();
EnableDiscordIntegration = new ReactiveObject<bool>(); EnableDiscordIntegration = new ReactiveObject<bool>();
CheckUpdatesOnStart = new ReactiveObject<bool>(); CheckUpdatesOnStart = new ReactiveObject<bool>();
UpdateCheckerType = new ReactiveObject<UpdaterType>();
ShowConfirmExit = new ReactiveObject<bool>(); ShowConfirmExit = new ReactiveObject<bool>();
RememberWindowState = new ReactiveObject<bool>(); RememberWindowState = new ReactiveObject<bool>();
ShowTitleBar = new ReactiveObject<bool>(); ShowTitleBar = new ReactiveObject<bool>();
@@ -46,6 +46,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
LoggingEnableTrace = Logger.EnableTrace, LoggingEnableTrace = Logger.EnableTrace,
LoggingEnableGuest = Logger.EnableGuest, LoggingEnableGuest = Logger.EnableGuest,
LoggingEnableFsAccessLog = Logger.EnableFsAccessLog, LoggingEnableFsAccessLog = Logger.EnableFsAccessLog,
LoggingEnableAvalonia = Logger.EnableAvaloniaLog,
LoggingFilteredClasses = Logger.FilteredClasses, LoggingFilteredClasses = Logger.FilteredClasses,
LoggingGraphicsDebugLevel = Logger.GraphicsDebugLevel, LoggingGraphicsDebugLevel = Logger.GraphicsDebugLevel,
SystemLanguage = System.Language, SystemLanguage = System.Language,
@@ -165,6 +166,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
Logger.EnableTrace.Value = false; Logger.EnableTrace.Value = false;
Logger.EnableGuest.Value = true; Logger.EnableGuest.Value = true;
Logger.EnableFsAccessLog.Value = false; Logger.EnableFsAccessLog.Value = false;
Logger.EnableAvaloniaLog.Value = false;
Logger.FilteredClasses.Value = []; Logger.FilteredClasses.Value = [];
Logger.GraphicsDebugLevel.Value = GraphicsDebugLevel.None; Logger.GraphicsDebugLevel.Value = GraphicsDebugLevel.None;
System.Language.Value = Language.AmericanEnglish; System.Language.Value = Language.AmericanEnglish;
@@ -173,7 +175,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
System.SystemTimeOffset.Value = 0; System.SystemTimeOffset.Value = 0;
System.EnableDockedMode.Value = true; System.EnableDockedMode.Value = true;
EnableDiscordIntegration.Value = true; EnableDiscordIntegration.Value = true;
CheckUpdatesOnStart.Value = true; UpdateCheckerType.Value = UpdaterType.PromptAtStartup;
ShowConfirmExit.Value = true; ShowConfirmExit.Value = true;
RememberWindowState.Value = true; RememberWindowState.Value = true;
ShowTitleBar.Value = !OperatingSystem.IsWindows(); ShowTitleBar.Value = !OperatingSystem.IsWindows();
@@ -0,0 +1,13 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Ava.Utilities.Configuration.UI
{
[JsonConverter(typeof(TypedStringEnumConverter<UpdaterType>))]
public enum UpdaterType
{
Off,
PromptAtStartup,
CheckInBackground
}
}
@@ -1,5 +1,4 @@
using Gommon; using Gommon;
using MsgPack;
using Ryujinx.Ava.Utilities.AppLibrary; using Ryujinx.Ava.Utilities.AppLibrary;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -1,7 +1,6 @@
using MsgPack; using MsgPack;
using Ryujinx.Ava.Utilities.AppLibrary; using Ryujinx.Ava.Utilities.AppLibrary;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.Ava.Utilities.PlayReport namespace Ryujinx.Ava.Utilities.PlayReport
{ {
@@ -1,4 +1,5 @@
using System; using Gommon;
using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -281,9 +282,14 @@ namespace Ryujinx.Ava.Utilities.PlayReport
players = players.OrderBy(p => p.Rank ?? int.MaxValue).ToList(); players = players.OrderBy(p => p.Rank ?? int.MaxValue).ToList();
return players.Count > 4 return players.Count > 4
? $"{players.Count} Players - " + string.Join(", ", ? $"{players.Count} Players - {
players.Take(3).Select(p => $"{p.Character}({p.PlayerNumber}){RankMedal(p.Rank)}")) players.Take(3)
: string.Join(", ", players.Select(p => $"{p.Character}({p.PlayerNumber}){RankMedal(p.Rank)}")); .Select(p => $"{p.Character}({p.PlayerNumber}){RankMedal(p.Rank)}")
.JoinToString(", ")
}"
: players
.Select(p => $"{p.Character}({p.PlayerNumber}){RankMedal(p.Rank)}")
.JoinToString(", ");
string RankMedal(int? rank) => rank switch string RankMedal(int? rank) => rank switch
{ {
@@ -1,9 +1,4 @@
using System; namespace Ryujinx.Ava.Utilities.PlayReport
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.Ava.Utilities.PlayReport
{ {
public static partial class PlayReports public static partial class PlayReports
{ {
+3 -4
View File
@@ -1,5 +1,4 @@
using FluentAvalonia.Core; using MsgPack;
using MsgPack;
using Ryujinx.Ava.Utilities.AppLibrary; using Ryujinx.Ava.Utilities.AppLibrary;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -153,7 +152,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
public override bool GetData(Horizon.Prepo.Types.PlayReport playReport, out object result) public override bool GetData(Horizon.Prepo.Types.PlayReport playReport, out object result)
{ {
List<MessagePackObject> packedObjects = []; List<MessagePackObject> packedObjects = [];
foreach (var reportKey in ReportKeys) foreach (string reportKey in ReportKeys)
{ {
if (!playReport.ReportData.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject)) if (!playReport.ReportData.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject))
{ {
@@ -177,7 +176,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
public override bool GetData(Horizon.Prepo.Types.PlayReport playReport, out object result) public override bool GetData(Horizon.Prepo.Types.PlayReport playReport, out object result)
{ {
Dictionary<string, MessagePackObject> packedObjects = []; Dictionary<string, MessagePackObject> packedObjects = [];
foreach (var reportKey in ReportKeys) foreach (string reportKey in ReportKeys)
{ {
if (!playReport.ReportData.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject)) if (!playReport.ReportData.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject))
continue; continue;