diff --git a/src/ARMeilleure/Memory/ReservedRegion.cs b/src/ARMeilleure/Memory/ReservedRegion.cs index a3ebd610d..dfe17c933 100644 --- a/src/ARMeilleure/Memory/ReservedRegion.cs +++ b/src/ARMeilleure/Memory/ReservedRegion.cs @@ -7,6 +7,7 @@ namespace ARMeilleure.Memory public const int DefaultGranularity = 65536; // Mapping granularity in Windows. public IJitMemoryBlock Block { get; } + public IJitMemoryAllocator Allocator { get; } public nint Pointer => Block.Pointer; @@ -21,6 +22,7 @@ namespace ARMeilleure.Memory granularity = DefaultGranularity; } + Allocator = allocator; Block = allocator.Reserve(maxSize); _maxSize = maxSize; _sizeGranularity = granularity; diff --git a/src/ARMeilleure/Translation/Cache/JitCache.cs b/src/ARMeilleure/Translation/Cache/JitCache.cs index d7e8201d8..bc75e7d3f 100644 --- a/src/ARMeilleure/Translation/Cache/JitCache.cs +++ b/src/ARMeilleure/Translation/Cache/JitCache.cs @@ -2,6 +2,8 @@ using ARMeilleure.CodeGen; using ARMeilleure.CodeGen.Unwinding; using ARMeilleure.Memory; using ARMeilleure.Native; +using Humanizer; +using Ryujinx.Common.Logging; using Ryujinx.Memory; using System; using System.Collections.Generic; @@ -18,9 +20,8 @@ namespace ARMeilleure.Translation.Cache private static readonly int _pageMask = _pageSize - 1; 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 CacheMemoryAllocator _cacheAllocator; @@ -30,6 +31,9 @@ namespace ARMeilleure.Translation.Cache private static readonly Lock _lock = new(); private static bool _initialized; + private static readonly List _jitRegions = new(); + private static int _activeRegionIndex = 0; + [SupportedOSPlatform("windows")] [LibraryImport("kernel32.dll", SetLastError = true)] public static partial nint FlushInstructionCache(nint hProcess, nint lpAddress, nuint dwSize); @@ -48,7 +52,9 @@ namespace ARMeilleure.Translation.Cache return; } - _jitRegion = new ReservedRegion(allocator, CacheSize); + ReservedRegion firstRegion = new(allocator, CacheSize); + _jitRegions.Add(firstRegion); + _activeRegionIndex = 0; if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS()) { @@ -59,7 +65,9 @@ namespace ARMeilleure.Translation.Cache if (OperatingSystem.IsWindows()) { - JitUnwindWindows.InstallFunctionTableHandler(_jitRegion.Pointer, CacheSize, _jitRegion.Pointer + Allocate(_pageSize)); + JitUnwindWindows.InstallFunctionTableHandler( + firstRegion.Pointer, CacheSize, firstRegion.Pointer + Allocate(_pageSize) + ); } _initialized = true; @@ -75,8 +83,8 @@ namespace ARMeilleure.Translation.Cache Debug.Assert(_initialized); int funcOffset = Allocate(code.Length); - - nint funcPtr = _jitRegion.Pointer + funcOffset; + ReservedRegion targetRegion = _jitRegions[_activeRegionIndex]; + nint funcPtr = targetRegion.Pointer + funcOffset; if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64) { @@ -90,9 +98,9 @@ namespace ARMeilleure.Translation.Cache } else { - ReprotectAsWritable(funcOffset, code.Length); + ReprotectAsWritable(targetRegion, funcOffset, 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) { @@ -116,52 +124,83 @@ namespace ARMeilleure.Translation.Cache { Debug.Assert(_initialized); - int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64()); - - if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset) + foreach (ReservedRegion region in _jitRegions) { - _cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size)); - _cacheEntries.RemoveAt(entryIndex); + if (pointer.ToInt64() < region.Pointer.ToInt64() || + 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 regionStart = offset & ~_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 regionStart = offset & ~_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) { codeSize = AlignCodeSize(codeSize); - int allocOffset = _cacheAllocator.Allocate(codeSize); - - if (allocOffset < 0) + for (int i = _activeRegionIndex; i < _jitRegions.Count; i++) { - 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) { return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1); @@ -185,18 +224,21 @@ namespace ARMeilleure.Translation.Cache { lock (_lock) { - int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default)); - - if (index < 0) + foreach (ReservedRegion _ in _jitRegions) { - index = ~index - 1; - } + int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default)); - if (index >= 0) - { - entry = _cacheEntries[index]; - entryIndex = index; - return true; + if (index < 0) + { + index = ~index - 1; + } + + if (index >= 0) + { + entry = _cacheEntries[index]; + entryIndex = index; + return true; + } } } diff --git a/src/ARMeilleure/Translation/PTC/PtcProfiler.cs b/src/ARMeilleure/Translation/PTC/PtcProfiler.cs index de0b78dbe..f3aaa58ff 100644 --- a/src/ARMeilleure/Translation/PTC/PtcProfiler.cs +++ b/src/ARMeilleure/Translation/PTC/PtcProfiler.cs @@ -144,17 +144,15 @@ namespace ARMeilleure.Translation.PTC public List GetBlacklistedFunctions() { - List funcs = new List(); + List funcs = []; - foreach (var profiledFunc in ProfiledFuncs) + foreach ((ulong ptr, FuncProfile funcProfile) in ProfiledFuncs) { - if (profiledFunc.Value.Blacklist) - { - if (!funcs.Contains(profiledFunc.Key)) - { - funcs.Add(profiledFunc.Key); - } - } + if (!funcProfile.Blacklist) + continue; + + if (!funcs.Contains(ptr)) + funcs.Add(ptr); } return funcs; diff --git a/src/Ryujinx.Cpu/LightningJit/Cache/JitCache.cs b/src/Ryujinx.Cpu/LightningJit/Cache/JitCache.cs index 5849401ab..ccf8ad964 100644 --- a/src/Ryujinx.Cpu/LightningJit/Cache/JitCache.cs +++ b/src/Ryujinx.Cpu/LightningJit/Cache/JitCache.cs @@ -1,4 +1,6 @@ using ARMeilleure.Memory; +using Humanizer; +using Ryujinx.Common.Logging; using Ryujinx.Memory; using System; using System.Collections.Generic; @@ -15,9 +17,8 @@ namespace Ryujinx.Cpu.LightningJit.Cache private static readonly int _pageMask = _pageSize - 1; 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 CacheMemoryAllocator _cacheAllocator; @@ -26,6 +27,8 @@ namespace Ryujinx.Cpu.LightningJit.Cache private static readonly Lock _lock = new(); private static bool _initialized; + private static readonly List _jitRegions = new(); + private static int _activeRegionIndex = 0; [SupportedOSPlatform("windows")] [LibraryImport("kernel32.dll", SetLastError = true)] @@ -45,7 +48,9 @@ namespace Ryujinx.Cpu.LightningJit.Cache return; } - _jitRegion = new ReservedRegion(allocator, CacheSize); + ReservedRegion firstRegion = new(allocator, CacheSize); + _jitRegions.Add(firstRegion); + _activeRegionIndex = 0; if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS()) { @@ -65,8 +70,8 @@ namespace Ryujinx.Cpu.LightningJit.Cache Debug.Assert(_initialized); int funcOffset = Allocate(code.Length); - - nint funcPtr = _jitRegion.Pointer + funcOffset; + ReservedRegion targetRegion = _jitRegions[_activeRegionIndex]; + nint funcPtr = targetRegion.Pointer + funcOffset; if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64) { @@ -80,18 +85,11 @@ namespace Ryujinx.Cpu.LightningJit.Cache } else { - ReprotectAsWritable(funcOffset, code.Length); - code.CopyTo(new Span((void*)funcPtr, code.Length)); - ReprotectAsExecutable(funcOffset, code.Length); + ReprotectAsWritable(targetRegion, funcOffset, code.Length); + Marshal.Copy(code.ToArray(), 0, funcPtr, code.Length); + ReprotectAsExecutable(targetRegion, funcOffset, code.Length); - if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64) - { - FlushInstructionCache(Process.GetCurrentProcess().Handle, funcPtr, (nuint)code.Length); - } - else - { - _jitCacheInvalidator?.Invalidate(funcPtr, (ulong)code.Length); - } + _jitCacheInvalidator?.Invalidate(funcPtr, (ulong)code.Length); } Add(funcOffset, code.Length); @@ -106,50 +104,80 @@ namespace Ryujinx.Cpu.LightningJit.Cache { Debug.Assert(_initialized); - int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64()); - - if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset) + foreach (ReservedRegion region in _jitRegions) { - _cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size)); - _cacheEntries.RemoveAt(entryIndex); + if (pointer.ToInt64() < region.Pointer.ToInt64() || + 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 regionStart = offset & ~_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 regionStart = offset & ~_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) { codeSize = AlignCodeSize(codeSize); - int allocOffset = _cacheAllocator.Allocate(codeSize); - - if (allocOffset < 0) + for (int i = _activeRegionIndex; i < _jitRegions.Count; i++) { - 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) diff --git a/src/Ryujinx.Cpu/LightningJit/Cache/NoWxCache.cs b/src/Ryujinx.Cpu/LightningJit/Cache/NoWxCache.cs index 1bbf70182..65d297c28 100644 --- a/src/Ryujinx.Cpu/LightningJit/Cache/NoWxCache.cs +++ b/src/Ryujinx.Cpu/LightningJit/Cache/NoWxCache.cs @@ -12,7 +12,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache { private const int CodeAlignment = 4; // Bytes. 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 // and allow the guest to take the fast path. diff --git a/src/Ryujinx.Graphics.Vulkan/Vendor.cs b/src/Ryujinx.Graphics.Vulkan/Vendor.cs index 87c6407cd..550ba0221 100644 --- a/src/Ryujinx.Graphics.Vulkan/Vendor.cs +++ b/src/Ryujinx.Graphics.Vulkan/Vendor.cs @@ -1,5 +1,4 @@ using Silk.NET.Vulkan; -using System.Text.RegularExpressions; namespace Ryujinx.Graphics.Vulkan { diff --git a/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs index b41bc60b1..2faff6e61 100644 --- a/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs +++ b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs @@ -15,7 +15,6 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; -using System.Text.RegularExpressions; namespace Ryujinx.HLE.HOS.Applets.Error { diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs index 3db2712f3..018bb8f14 100644 --- a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs @@ -150,6 +150,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl { BsdSocketOption.SoLinger, SocketOptionName.Linger }, { BsdSocketOption.SoOobInline, SocketOptionName.OutOfBandInline }, { BsdSocketOption.SoReusePort, SocketOptionName.ReuseAddress }, + { BsdSocketOption.SoNoSigpipe, SocketOptionName.DontLinger }, { BsdSocketOption.SoSndBuf, SocketOptionName.SendBuffer }, { BsdSocketOption.SoRcvBuf, SocketOptionName.ReceiveBuffer }, { BsdSocketOption.SoSndLoWat, SocketOptionName.SendLowWater }, diff --git a/src/Ryujinx.Horizon/HorizonStatic.cs b/src/Ryujinx.Horizon/HorizonStatic.cs index eb9dd4e31..a936a5a2d 100644 --- a/src/Ryujinx.Horizon/HorizonStatic.cs +++ b/src/Ryujinx.Horizon/HorizonStatic.cs @@ -1,4 +1,3 @@ -using MsgPack; using Ryujinx.Horizon.Common; using Ryujinx.Horizon.Prepo.Types; using Ryujinx.Memory; diff --git a/src/Ryujinx/Assets/locales.json b/src/Ryujinx/Assets/locales.json index 7f6bf5b56..df0cdb566 100644 --- a/src/Ryujinx/Assets/locales.json +++ b/src/Ryujinx/Assets/locales.json @@ -5822,6 +5822,31 @@ "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", "Translations": { @@ -16797,6 +16822,31 @@ "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", "Translations": { diff --git a/src/Ryujinx/DiscordIntegrationModule.cs b/src/Ryujinx/DiscordIntegrationModule.cs index 1f820a223..47fc8ad69 100644 --- a/src/Ryujinx/DiscordIntegrationModule.cs +++ b/src/Ryujinx/DiscordIntegrationModule.cs @@ -1,6 +1,5 @@ using DiscordRPC; using Gommon; -using MsgPack; using Ryujinx.Ava.Utilities; using Ryujinx.Ava.Utilities.AppLibrary; using Ryujinx.Ava.Utilities.Configuration; @@ -11,7 +10,6 @@ using Ryujinx.HLE; using Ryujinx.HLE.Loaders.Processes; using Ryujinx.Horizon; using Ryujinx.Horizon.Prepo.Types; -using System.Linq; using System.Text; namespace Ryujinx.Ava diff --git a/src/Ryujinx/Headless/Options.cs b/src/Ryujinx/Headless/Options.cs index 1b10145fe..a57863d5d 100644 --- a/src/Ryujinx/Headless/Options.cs +++ b/src/Ryujinx/Headless/Options.cs @@ -387,7 +387,7 @@ namespace Ryujinx.Headless [Option("graphics-shaders-dump-path", Required = false, HelpText = "Dumps shaders in this local directory. (Developer only)")] 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; } [Option("preferred-gpu-vendor", Required = false, Default = "", HelpText = "When using the Vulkan backend, prefer using the GPU from the specified vendor.")] diff --git a/src/Ryujinx/UI/Controls/ApplicationDataView.axaml.cs b/src/Ryujinx/UI/Controls/ApplicationDataView.axaml.cs index e85e1188e..57dba3228 100644 --- a/src/Ryujinx/UI/Controls/ApplicationDataView.axaml.cs +++ b/src/Ryujinx/UI/Controls/ApplicationDataView.axaml.cs @@ -1,12 +1,9 @@ -using Avalonia; -using Avalonia.Controls; +using Avalonia.Controls; using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.Styling; using FluentAvalonia.UI.Controls; -using Ryujinx.Ava; using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Controls; using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.Windows; diff --git a/src/Ryujinx/UI/Controls/ApplicationListView.axaml.cs b/src/Ryujinx/UI/Controls/ApplicationListView.axaml.cs index 7c6b0cf15..fb38fdb72 100644 --- a/src/Ryujinx/UI/Controls/ApplicationListView.axaml.cs +++ b/src/Ryujinx/UI/Controls/ApplicationListView.axaml.cs @@ -7,7 +7,6 @@ using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.Utilities.AppLibrary; using Ryujinx.Ava.Utilities.Compat; using System; -using System.Globalization; using System.Linq; namespace Ryujinx.Ava.UI.Controls diff --git a/src/Ryujinx/UI/Helpers/LoggerAdapter.cs b/src/Ryujinx/UI/Helpers/LoggerAdapter.cs index 928d1fc31..ba317e74a 100644 --- a/src/Ryujinx/UI/Helpers/LoggerAdapter.cs +++ b/src/Ryujinx/UI/Helpers/LoggerAdapter.cs @@ -1,6 +1,7 @@ using Avalonia.Logging; using Avalonia.Utilities; using Gommon; +using Ryujinx.Ava.Utilities.Configuration; using Ryujinx.Common.Logging; using System; using System.Text; @@ -14,13 +15,19 @@ namespace Ryujinx.Ava.UI.Helpers internal class LoggerAdapter : ILogSink { + private static bool _avaloniaLogsEnabled = ConfigurationState.Instance.Logger.EnableAvaloniaLog; + public static void Register() { AvaLogger.Sink = new LoggerAdapter(); + ConfigurationState.Instance.Logger.EnableAvaloniaLog.Event + += (_, e) => _avaloniaLogsEnabled = e.NewValue; } private static RyuLogger.Log? GetLog(AvaLogLevel level, string area) { + if (!_avaloniaLogsEnabled) return null; + return level switch { AvaLogLevel.Verbose => RyuLogger.Debug, diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index 73455aa24..68bb5a1cd 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -1147,10 +1147,10 @@ namespace Ryujinx.Ava.UI.ViewModels List dirs = result.Select(it => it.Path.LocalPath).ToList(); 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[localeMessageAddedKey], numAdded) - }); + ); await Dispatcher.UIThread.InvokeAsync(async () => { diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index 5305a3f72..4903ecf0b 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -247,6 +247,7 @@ namespace Ryujinx.Ava.UI.ViewModels public bool EnableTrace { get; set; } public bool EnableGuest { get; set; } public bool EnableFsAccessLog { get; set; } + public bool EnableAvaloniaLog { get; set; } public bool EnableDebug { get; set; } public bool IsOpenAlEnabled { get; set; } public bool IsSoundIoEnabled { get; set; } @@ -652,6 +653,7 @@ namespace Ryujinx.Ava.UI.ViewModels EnableGuest = config.Logger.EnableGuest; EnableDebug = config.Logger.EnableDebug; EnableFsAccessLog = config.Logger.EnableFsAccessLog; + EnableAvaloniaLog = config.Logger.EnableAvaloniaLog; FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode; OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value; @@ -775,6 +777,7 @@ namespace Ryujinx.Ava.UI.ViewModels config.Logger.EnableGuest.Value = EnableGuest; config.Logger.EnableDebug.Value = EnableDebug; config.Logger.EnableFsAccessLog.Value = EnableFsAccessLog; + config.Logger.EnableAvaloniaLog.Value = EnableAvaloniaLog; config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode; config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel; diff --git a/src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml index 7ab49c0ae..d28f0ffa7 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml +++ b/src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml @@ -74,6 +74,10 @@ ToolTip.Tip="{ext:Locale DebugLogTooltip}"> + + + ValueFormatUtils.FormatTimeSpan(TimePlayed); - public bool HasPlayedPreviously => TimePlayedString != string.Empty; + public bool HasPlayedPreviously => TimePlayed.TotalSeconds > 1; public string LastPlayedString => ValueFormatUtils.FormatDateTime(LastPlayed)?.Replace(" ", "\n"); @@ -79,25 +79,6 @@ namespace Ryujinx.Ava.Utilities.AppLibrary public LocaleKeys? PlayabilityStatus => Compatibility.Convert(x => x.Status).OrElse(null); - public string CompatibilityToolTip - { - get - { - StringBuilder sb = new(LocalizedStatusTooltip); - - string formattedCompatibilityLabels = FormattedCompatibilityLabels; - if (!string.IsNullOrEmpty(formattedCompatibilityLabels)) - { - sb.AppendLine() - .AppendLine() - .Append(formattedCompatibilityLabels); - } - - return sb.ToString(); - } - } - - public string LocalizedStatusTooltip => Compatibility.Convert(x => #pragma warning disable CS8509 It is exhaustive for all possible values this can contain. @@ -121,16 +102,16 @@ namespace Ryujinx.Ava.Utilities.AppLibrary 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)) { Logger.Error?.Print(LogClass.Application, $"File \"{titleFilePath}\" does not exist."); 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(); diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationFileFormat.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationFileFormat.cs index 805a171be..95364873b 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationFileFormat.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationFileFormat.cs @@ -15,7 +15,7 @@ namespace Ryujinx.Ava.Utilities.Configuration /// /// The current version of the file format /// - public const int CurrentVersion = 63; + public const int CurrentVersion = 64; /// /// Version of the configuration file format @@ -111,6 +111,11 @@ namespace Ryujinx.Ava.Utilities.Configuration /// Enables printing FS access log messages /// public bool LoggingEnableFsAccessLog { get; set; } + + /// + /// Enables log messages from Avalonia + /// + public bool LoggingEnableAvalonia { get; set; } /// /// Controls which log messages are written to the log targets diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs index 8a1b03853..18f801a0e 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs @@ -437,7 +437,8 @@ namespace Ryujinx.Ava.Utilities.Configuration } }), (62, static cff => cff.RainbowSpeed = 1f), - (63, static cff => cff.MatchSystemTime = false) + (63, static cff => cff.MatchSystemTime = false), + (64, static cff => cff.LoggingEnableAvalonia = false) ); } } diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs index 2d6e2aa7f..8fbe20e05 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs @@ -254,6 +254,11 @@ namespace Ryujinx.Ava.Utilities.Configuration /// Enables printing FS access log messages /// public ReactiveObject EnableFsAccessLog { get; private set; } + + /// + /// Enables log messages from Avalonia + /// + public ReactiveObject EnableAvaloniaLog { get; private set; } /// /// Controls which log messages are written to the log targets @@ -281,6 +286,7 @@ namespace Ryujinx.Ava.Utilities.Configuration EnableTrace = new ReactiveObject(); EnableGuest = new ReactiveObject(); EnableFsAccessLog = new ReactiveObject(); + EnableAvaloniaLog = new ReactiveObject(); FilteredClasses = new ReactiveObject(); EnableFileLog = new ReactiveObject(); EnableFileLog.LogChangesToValue(nameof(EnableFileLog)); diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs index 4ab77a60f..f8fbc90d8 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs @@ -46,6 +46,7 @@ namespace Ryujinx.Ava.Utilities.Configuration LoggingEnableTrace = Logger.EnableTrace, LoggingEnableGuest = Logger.EnableGuest, LoggingEnableFsAccessLog = Logger.EnableFsAccessLog, + LoggingEnableAvalonia = Logger.EnableAvaloniaLog, LoggingFilteredClasses = Logger.FilteredClasses, LoggingGraphicsDebugLevel = Logger.GraphicsDebugLevel, SystemLanguage = System.Language, @@ -165,6 +166,7 @@ namespace Ryujinx.Ava.Utilities.Configuration Logger.EnableTrace.Value = false; Logger.EnableGuest.Value = true; Logger.EnableFsAccessLog.Value = false; + Logger.EnableAvaloniaLog.Value = false; Logger.FilteredClasses.Value = []; Logger.GraphicsDebugLevel.Value = GraphicsDebugLevel.None; System.Language.Value = Language.AmericanEnglish; diff --git a/src/Ryujinx/Utilities/PlayReport/Analyzer.cs b/src/Ryujinx/Utilities/PlayReport/Analyzer.cs index 0b5284673..51047cc32 100644 --- a/src/Ryujinx/Utilities/PlayReport/Analyzer.cs +++ b/src/Ryujinx/Utilities/PlayReport/Analyzer.cs @@ -1,5 +1,4 @@ using Gommon; -using MsgPack; using Ryujinx.Ava.Utilities.AppLibrary; using System; using System.Collections.Generic; diff --git a/src/Ryujinx/Utilities/PlayReport/MatchedValues.cs b/src/Ryujinx/Utilities/PlayReport/MatchedValues.cs index 3086a9d65..46cae678a 100644 --- a/src/Ryujinx/Utilities/PlayReport/MatchedValues.cs +++ b/src/Ryujinx/Utilities/PlayReport/MatchedValues.cs @@ -1,7 +1,6 @@ using MsgPack; using Ryujinx.Ava.Utilities.AppLibrary; using System.Collections.Generic; -using System.Linq; namespace Ryujinx.Ava.Utilities.PlayReport { diff --git a/src/Ryujinx/Utilities/PlayReport/PlayReports.Formatters.cs b/src/Ryujinx/Utilities/PlayReport/PlayReports.Formatters.cs index f0d7f87ba..cabace6bb 100644 --- a/src/Ryujinx/Utilities/PlayReport/PlayReports.Formatters.cs +++ b/src/Ryujinx/Utilities/PlayReport/PlayReports.Formatters.cs @@ -1,4 +1,5 @@ -using System; +using Gommon; +using System; using System.Buffers.Binary; using System.Collections.Generic; using System.Linq; @@ -281,9 +282,14 @@ namespace Ryujinx.Ava.Utilities.PlayReport players = players.OrderBy(p => p.Rank ?? int.MaxValue).ToList(); return players.Count > 4 - ? $"{players.Count} Players - " + string.Join(", ", - players.Take(3).Select(p => $"{p.Character}({p.PlayerNumber}){RankMedal(p.Rank)}")) - : string.Join(", ", players.Select(p => $"{p.Character}({p.PlayerNumber}){RankMedal(p.Rank)}")); + ? $"{players.Count} Players - { + players.Take(3) + .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 { diff --git a/src/Ryujinx/Utilities/PlayReport/PlayReports.cs b/src/Ryujinx/Utilities/PlayReport/PlayReports.cs index 5f6ba3446..d5568962e 100644 --- a/src/Ryujinx/Utilities/PlayReport/PlayReports.cs +++ b/src/Ryujinx/Utilities/PlayReport/PlayReports.cs @@ -1,9 +1,4 @@ -using System; -using System.Buffers.Binary; -using System.Collections.Generic; -using System.Linq; - -namespace Ryujinx.Ava.Utilities.PlayReport +namespace Ryujinx.Ava.Utilities.PlayReport { public static partial class PlayReports { diff --git a/src/Ryujinx/Utilities/PlayReport/Specs.cs b/src/Ryujinx/Utilities/PlayReport/Specs.cs index b81a599a2..f1b94de68 100644 --- a/src/Ryujinx/Utilities/PlayReport/Specs.cs +++ b/src/Ryujinx/Utilities/PlayReport/Specs.cs @@ -1,5 +1,4 @@ -using FluentAvalonia.Core; -using MsgPack; +using MsgPack; using Ryujinx.Ava.Utilities.AppLibrary; using System; 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) { List packedObjects = []; - foreach (var reportKey in ReportKeys) + foreach (string reportKey in ReportKeys) { 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) { Dictionary packedObjects = []; - foreach (var reportKey in ReportKeys) + foreach (string reportKey in ReportKeys) { if (!playReport.ReportData.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject)) continue;