Compare commits

...

5 Commits

Author SHA1 Message Date
Max
4631e0a9e1 Configured the garbage collector for lower spikes (#88)
Did you know that the garbage collector's default settings are designed for single-threaded applications and quick start-up times? We don't care about either of these.

Configured:
- Concurrent GC (default is true) (set for clarity)
- RetainVM: segments that should be deleted are put on a standby list for future use (default is false)
- QuickJit: enabling quick JIT decreases startup time but can produce code with degraded performance characteristics; for example, the code may use more stack space, allocate more memory, and run slower. (default is true) (disabled)
- ReadyToRun: configures whether the .NET runtime uses pre-compiled code for images with available ReadyToRun data; disabling this option forces the runtime to JIT-compile framework code. (default is true) (set to false, we dont publish with this option anyway)
- TieredPGO: this setting enables dynamic (tiered) profile-guided optimization (PGO) in .NET 6 and later versions. If quick JIT is disabled but tiered compilation is enabled, only pre-compiled code participates in tiered compilation. If a method is not pre-compiled with ReadyToRun, the JIT behavior is the same as if tiered compilation were disabled.

Features:
- Set ``GCLatencyMode.Interactive`` when in-menu and emulator is paused, otherwise uses ``GCLatencyMode.LowLatency``.
- Added a new UI option in the Settings > CPU menu to toggle ``GCLatencyMode.LowLatency`` during guest runtime.

![image](/attachments/84ffc6c6-d92c-4ec5-8a95-9d72dc6f1b04)

Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/88
2026-05-22 23:12:19 +00:00
KeatonTheBot
e477ec7149 CI: Re-enable win-arm64 builds (#12)
Re-enable win-arm64 builds in CI now that they have been fixed with file trimming.

Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/12
2026-05-22 21:47:41 +00:00
Mabel
81468c1d25 Discord Rich Presence: Tomodachi Life LTD and Animal Crossing New Horizons (#103)
Adds Discord Rich Presence for Tomodachi Life: Living the Dream, Tomodachi Life: Living the Dream - Welcome Version, and Animal Crossing: New Horizons

Tomodachi Life Rich Presence uses your total Mii count, and your island level
![image](/attachments/46c20fba-f092-4f8c-af8d-c71340d94e78)
Animal Crossing New Horizons Rich Presence uses your island name
![image](/attachments/f9dfbeaa-86a3-4989-b74a-d65b1d8e6260)

Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/103
2026-05-20 13:38:57 +00:00
Max
004a12005e UI: Included more launch checks (#4)
- Added Onedrive folder check for Windows.
- Added iCloud and Downloads folder checks for macOS.
- Added sudo checks for macOS/Linux.
- Added dialogue prompts for macOS/Linux.
- Added unofficial build warning for detected Flatpak install.

These checks have console fallbacks in case the GUI decides it doesn't want to work that day.

Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/4
2026-05-20 13:37:05 +00:00
Mabel
a3eda287b5 Fix Clipboard Copy Operation Crash (#108)
Fixes a COM exception crash related to clipboard copy events from changes in Avalonia 11.3

Solves [Ryubing/Issues#294](https://github.com/Ryubing/Issues/issues/294) caused by Avalonia 11.3's changes, see [AvaloniaUI/Avalonia#20007](https://github.com/AvaloniaUI/Avalonia/issues/20007) for more information

I've only tested this on Windows, no idea if this has issues in MacOS or Linux, or if it's even a problem there at all.

Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/108
2026-05-20 13:22:56 +00:00
19 changed files with 345 additions and 17 deletions

View File

@@ -28,7 +28,7 @@ jobs:
configuration: [Release] configuration: [Release]
platform: platform:
- { name: win-x64, zip_os_name: win_x64 } - { name: win-x64, zip_os_name: win_x64 }
#- { name: win-arm64, zip_os_name: win_arm64 } - { name: win-arm64, zip_os_name: win_arm64 }
- { name: linux-x64, zip_os_name: linux_x64 } - { name: linux-x64, zip_os_name: linux_x64 }
- { name: linux-arm64, zip_os_name: linux_arm64 } - { name: linux-arm64, zip_os_name: linux_arm64 }
#- { name: osx-x64, zip_os_name: osx_x64 } #- { name: osx-x64, zip_os_name: osx_x64 }

View File

@@ -32,7 +32,7 @@ jobs:
matrix: matrix:
platform: platform:
- { name: win-x64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: win_x64 } - { name: win-x64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: win_x64 }
#- { name: win-arm64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: win_arm64 } - { name: win-arm64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: win_arm64 }
- { name: linux-x64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: linux_x64 } - { name: linux-x64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: linux_x64 }
- { name: linux-arm64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: linux_arm64 } - { name: linux-arm64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: linux_arm64 }
steps: steps:

View File

@@ -26,7 +26,7 @@ jobs:
matrix: matrix:
platform: platform:
- { name: win-x64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: win_x64 } - { name: win-x64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: win_x64 }
#- { name: win-arm64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: win_arm64 } - { name: win-arm64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: win_arm64 }
- { name: linux-x64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: linux_x64 } - { name: linux-x64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: linux_x64 }
- { name: linux-arm64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: linux_arm64 } - { name: linux-arm64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: linux_arm64 }
steps: steps:

View File

@@ -200,6 +200,31 @@
"zh_TW": "使用 Hypervisor" "zh_TW": "使用 Hypervisor"
} }
}, },
{
"ID": "SettingsTabSystemGCLowLatency",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Use Low-latency Garbage Collector",
"es_ES": "Usa recolección de basura de baja latencia",
"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": "MenuBarFile", "ID": "MenuBarFile",
"Translations": { "Translations": {
@@ -16550,6 +16575,31 @@
"zh_TW": "變更客體記憶體的映射和存取方式。這會極大地影響模擬 CPU 效能。\n\n如果不確定請設定為主體略過檢查模式。" "zh_TW": "變更客體記憶體的映射和存取方式。這會極大地影響模擬 CPU 效能。\n\n如果不確定請設定為主體略過檢查模式。"
} }
}, },
{
"ID": "GCLowLatencyTooltip",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Sets the garbage collector for the CLR to low-latency mode.\n\nThis may decrease stuttering at the cost of performance.\n\nLeave OFF if unsure.",
"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": "MemoryManagerSoftwareTooltip", "ID": "MemoryManagerSoftwareTooltip",
"Translations": { "Translations": {

View File

@@ -133,9 +133,9 @@ namespace Ryujinx.HLE.HOS.Services.Caps
using SKBitmap bitmap = new(new SKImageInfo(ScreenshotWidth, ScreenshotHeight, SKColorType.Rgba8888)); using SKBitmap bitmap = new(new SKImageInfo(ScreenshotWidth, ScreenshotHeight, SKColorType.Rgba8888));
IntPtr pixels = bitmap.GetPixels(); nint pixels = bitmap.GetPixels();
if (pixels == IntPtr.Zero) if (pixels == 0)
{ {
return ResultCode.InvalidArgument; return ResultCode.InvalidArgument;
} }

View File

@@ -46,6 +46,7 @@ namespace Ryujinx.Ava
private const uint MbIconwarning = 0x30; private const uint MbIconwarning = 0x30;
[STAThread]
public static int Main(string[] args) public static int Main(string[] args)
{ {
Version = ReleaseInformation.Version; Version = ReleaseInformation.Version;
@@ -54,17 +55,39 @@ namespace Ryujinx.Ava
{ {
if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041)) if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041))
{ {
_ = Win32NativeInterop.MessageBoxA(nint.Zero, "You are running an outdated version of Windows.\n\nRyujinx supports Windows 10 version 20H1 and newer.\n", $"Ryujinx {Version}", MbIconwarning); Logger.Error?.PrintMsg(LogClass.Application, "Ryujinx is not intended to be run on an outdated version of Windows. Exiting...");
_ = Win32NativeInterop.MessageBoxA(nint.Zero,
"You are running an outdated version of Windows.\n\nRyujinx supports Windows 10 version 20H1 and newer.\n",
$"Ryujinx {Version}", MbIconwarning);
return 0; return 0;
} }
var programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); string onedriveFiles = Environment.GetEnvironmentVariable("Onedrive");
var programFilesX86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86); string onedriveConsumerFiles = Environment.GetEnvironmentVariable("OnedriveConsumer");
string onedriveCommercialFiles = Environment.GetEnvironmentVariable("OnedriveCommercial");
// Apparently not everyone has OneDrive shoved onto their system.
if ((onedriveFiles is not null && Environment.CurrentDirectory.StartsWithIgnoreCase(onedriveFiles))
|| (onedriveConsumerFiles is not null && Environment.CurrentDirectory.StartsWithIgnoreCase(onedriveConsumerFiles))
|| (onedriveCommercialFiles is not null && Environment.CurrentDirectory.StartsWithIgnoreCase(onedriveCommercialFiles)))
{
Logger.Error?.PrintMsg(LogClass.Application, "Ryujinx is not intended to be run from a OneDrive folder. Exiting...");
_ = Win32NativeInterop.MessageBoxA(nint.Zero,
"Ryujinx is not intended to be run from a OneDrive folder. Please move it out and relaunch.",
$"Ryujinx {Version}", MbIconwarning);
return 0;
}
string programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
string programFilesX86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
if (Environment.CurrentDirectory.StartsWithIgnoreCase(programFiles) || if (Environment.CurrentDirectory.StartsWithIgnoreCase(programFiles) ||
Environment.CurrentDirectory.StartsWithIgnoreCase(programFilesX86)) Environment.CurrentDirectory.StartsWithIgnoreCase(programFilesX86))
{ {
_ = Win32NativeInterop.MessageBoxA(nint.Zero, "Ryujinx is not intended to be run from the Program Files folder. Please move it out and relaunch.", $"Ryujinx {Version}", MbIconwarning); Logger.Error?.PrintMsg(LogClass.Application, "Ryujinx is not intended to be run from the Program Files folder. Exiting...");
_ = Win32NativeInterop.MessageBoxA(nint.Zero,
"Ryujinx is not intended to be run from the Program Files folder. Please move it out and relaunch.",
$"Ryujinx {Version}", MbIconwarning);
return 0; return 0;
} }
@@ -74,10 +97,70 @@ namespace Ryujinx.Ava
// ...but this reads like it checks if the current is in/has the Windows admin role? lol // ...but this reads like it checks if the current is in/has the Windows admin role? lol
if (new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)) if (new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator))
{ {
_ = Win32NativeInterop.MessageBoxA(nint.Zero, "Ryujinx is not intended to be run as administrator.", $"Ryujinx {Version}", MbIconwarning); Logger.Error?.PrintMsg(LogClass.Application, "Ryujinx is not intended to be run as administrator. Exiting...");
_ = Win32NativeInterop.MessageBoxA(nint.Zero, "Ryujinx is not intended to be run as administrator.",
$"Ryujinx {Version}", MbIconwarning);
return 0; return 0;
} }
} }
else // Unix
{
// sudo check
[DllImport("libc")]
static extern uint geteuid();
bool root = geteuid().Equals(0);
if (OperatingSystem.IsMacOS())
{
if (root)
{
Logger.Error?.PrintMsg(LogClass.Application, "Ryujinx is not intended to be run as administrator. Exiting...");
macOSNativeInterop.SimpleMessageBox($"Ryujinx {Version}",
"Ryujinx is not intended to be run as administrator.", "Ok");
return 0;
}
string downloadFiles = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Downloads");
if (Environment.CurrentDirectory.StartsWithIgnoreCase(downloadFiles))
{
Logger.Error?.PrintMsg(LogClass.Application, "Ryujinx is not intended to be run from the Downloads folder. Exiting...");
macOSNativeInterop.SimpleMessageBox($"Ryujinx {Version}",
"Ryujinx is not intended to be run from the Downloads folder.", "Ok");
return 0;
}
string icloudFiles = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library/Mobile Documents/com~apple~CloudDocs");
if (Environment.CurrentDirectory.StartsWithIgnoreCase(icloudFiles))
{
Logger.Error?.PrintMsg(LogClass.Application, "Ryujinx is not intended to be run from the iCloud folder. Exiting...");
macOSNativeInterop.SimpleMessageBox($"Ryujinx {Version}",
"Ryujinx is not intended to be run from the iCloud folder.", "Ok");
return 0;
}
}
if (OperatingSystem.IsLinux())
{
if (root)
{
Logger.Error?.PrintMsg(LogClass.Application, "Ryujinx is not intended to be run as administrator. Exiting...");
LinuxSDLInterop.SimpleMessageBox($"Ryujinx {Version}", "Ryujinx is not intended to be run as administrator.");
return 0;
}
string container = Environment.GetEnvironmentVariable("container");
if (container is not null && container.EqualsIgnoreCase("flatpak"))
{
Logger.Info?.PrintMsg(LogClass.Application, "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=");
Logger.Warning?.PrintMsg(LogClass.Application, "This is very likely an unofficial build, Ryujinx does NOT have a flatpak!");
Logger.Info?.PrintMsg(LogClass.Application, "Please visit https://ryujinx.app/ for our official builds.");
Logger.Info?.PrintMsg(LogClass.Application, "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=");
}
}
}
bool noGuiArg = ConsumeCommandLineArgument(ref args, "--no-gui") || ConsumeCommandLineArgument(ref args, "nogui"); bool noGuiArg = ConsumeCommandLineArgument(ref args, "--no-gui") || ConsumeCommandLineArgument(ref args, "nogui");
bool coreDumpArg = ConsumeCommandLineArgument(ref args, "--core-dumps"); bool coreDumpArg = ConsumeCommandLineArgument(ref args, "--core-dumps");
@@ -115,6 +198,12 @@ namespace Ryujinx.Ava
public static AppBuilder BuildAvaloniaApp() => public static AppBuilder BuildAvaloniaApp() =>
AppBuilder.Configure<RyujinxApp>() AppBuilder.Configure<RyujinxApp>()
.UsePlatformDetect() .UsePlatformDetect()
// Vulkan UI rendering performs better, but its unpolished, and as such it lacks effective transparency.
// https://github.com/AvaloniaUI/Avalonia/issues/19378
// https://github.com/AvaloniaUI/Avalonia/issues/9610
// X11RenderingMode.Glx && X11RenderingMode.Egl, Win32RenderingMode.Vulkan have these issues.
.With(new X11PlatformOptions .With(new X11PlatformOptions
{ {
EnableMultiTouch = true, EnableMultiTouch = true,
@@ -310,7 +399,7 @@ namespace Ryujinx.Ava
"never" => HideCursorMode.Never, "never" => HideCursorMode.Never,
"onidle" => HideCursorMode.OnIdle, "onidle" => HideCursorMode.OnIdle,
"always" => HideCursorMode.Always, "always" => HideCursorMode.Always,
_ => ConfigurationState.Instance.HideCursor, _ => ConfigurationState.Instance.HideCursor
}; };
// Check if memoryManagerMode was overridden. // Check if memoryManagerMode was overridden.

View File

@@ -46,6 +46,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Runtime;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -579,6 +580,12 @@ namespace Ryujinx.Ava.Systems
{ {
_isActive = false; _isActive = false;
_playTimer.Stop(); _playTimer.Stop();
GCSettings.LatencyMode = GCLatencyMode.Interactive;
if (ConfigurationState.Instance.System.GCLowLatency)
{
Logger.Info?.Print(LogClass.Application, "Garbage collector set to interactive mode.");
}
} }
private void Exit() private void Exit()
@@ -662,6 +669,12 @@ namespace Ryujinx.Ava.Systems
_chrono.Stop(); _chrono.Stop();
_playTimer.Stop(); _playTimer.Stop();
GCSettings.LatencyMode = GCLatencyMode.Interactive;
if (ConfigurationState.Instance.System.GCLowLatency)
{
Logger.Info?.Print(LogClass.Application, "Garbage collector set to interactive mode.");
}
} }
public void DisposeGpu() public void DisposeGpu()
@@ -915,7 +928,14 @@ namespace Ryujinx.Ava.Systems
ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText,
appMetadata => appMetadata.UpdatePreGame() appMetadata => appMetadata.UpdatePreGame()
); );
_playTimer.Start(); _playTimer.Start();
if (ConfigurationState.Instance.System.GCLowLatency)
{
GCSettings.LatencyMode = GCLatencyMode.LowLatency;
Logger.Info?.Print(LogClass.Application, "Garbage collector set to low latency mode.");
}
} }
internal void Resume() internal void Resume()
@@ -926,6 +946,12 @@ namespace Ryujinx.Ava.Systems
_playTimer.Start(); _playTimer.Start();
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowOldUI); _viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowOldUI);
Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed."); Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed.");
if (ConfigurationState.Instance.System.GCLowLatency)
{
GCSettings.LatencyMode = GCLatencyMode.LowLatency;
Logger.Info?.Print(LogClass.Application, "Garbage collector set to low latency mode.");
}
} }
internal void Pause() internal void Pause()
@@ -936,6 +962,12 @@ namespace Ryujinx.Ava.Systems
_playTimer.Stop(); _playTimer.Stop();
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowOldUI, LocaleManager.Instance[LocaleKeys.Paused]); _viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowOldUI, LocaleManager.Instance[LocaleKeys.Paused]);
Logger.Info?.Print(LogClass.Emulation, "Emulation was paused."); Logger.Info?.Print(LogClass.Emulation, "Emulation was paused.");
GCSettings.LatencyMode = GCLatencyMode.Interactive;
if (ConfigurationState.Instance.System.GCLowLatency)
{
Logger.Info?.Print(LogClass.Application, "Garbage collector set to interactive mode.");
}
} }
private void InitEmulatedSwitch() private void InitEmulatedSwitch()

View File

@@ -471,6 +471,11 @@ namespace Ryujinx.Ava.Systems.Configuration
/// </summary> /// </summary>
public bool UseHypervisor { get; set; } public bool UseHypervisor { get; set; }
/// <summary>
/// Enable or disable low-latency mode for garbage collection
/// </summary>
public bool GCLowLatency { get; set; }
/// <summary> /// <summary>
/// Enables or disables the GDB stub /// Enables or disables the GDB stub
/// </summary> /// </summary>

View File

@@ -112,6 +112,7 @@ namespace Ryujinx.Ava.Systems.Configuration
System.IgnoreControllerApplet.Value = cff.IgnoreApplet; System.IgnoreControllerApplet.Value = cff.IgnoreApplet;
System.SkipUserProfilesManager.Value = cff.SkipUserProfiles; System.SkipUserProfilesManager.Value = cff.SkipUserProfiles;
System.UseHypervisor.Value = cff.UseHypervisor; System.UseHypervisor.Value = cff.UseHypervisor;
System.GCLowLatency.Value = cff.GCLowLatency;
UI.GuiColumns.FavColumn.Value = shouldLoadFromFile ? cff.GuiColumns.FavColumn : UI.GuiColumns.FavColumn.Value; UI.GuiColumns.FavColumn.Value = shouldLoadFromFile ? cff.GuiColumns.FavColumn : UI.GuiColumns.FavColumn.Value;
UI.GuiColumns.IconColumn.Value = shouldLoadFromFile ? cff.GuiColumns.IconColumn : UI.GuiColumns.IconColumn.Value; UI.GuiColumns.IconColumn.Value = shouldLoadFromFile ? cff.GuiColumns.IconColumn : UI.GuiColumns.IconColumn.Value;
@@ -535,7 +536,8 @@ namespace Ryujinx.Ava.Systems.Configuration
{ {
if (cff.AudioBackend is AudioBackend.SDL2) if (cff.AudioBackend is AudioBackend.SDL2)
cff.AudioBackend = AudioBackend.SDL3; cff.AudioBackend = AudioBackend.SDL3;
}) }),
(72, static cff => cff.GCLowLatency = false)
); );
} }
} }

View File

@@ -418,6 +418,11 @@ namespace Ryujinx.Ava.Systems.Configuration
/// </summary> /// </summary>
public ReactiveObject<bool> UseHypervisor { get; private set; } public ReactiveObject<bool> UseHypervisor { get; private set; }
/// <summary>
/// Enable or disable low-latency garbage collection
/// </summary>
public ReactiveObject<bool> GCLowLatency { get; private set; }
public SystemSection() public SystemSection()
{ {
Language = new ReactiveObject<Language>(); Language = new ReactiveObject<Language>();
@@ -471,6 +476,8 @@ namespace Ryujinx.Ava.Systems.Configuration
AudioVolume.LogChangesToValue(nameof(AudioVolume)); AudioVolume.LogChangesToValue(nameof(AudioVolume));
UseHypervisor = new ReactiveObject<bool>(); UseHypervisor = new ReactiveObject<bool>();
UseHypervisor.LogChangesToValue(nameof(UseHypervisor)); UseHypervisor.LogChangesToValue(nameof(UseHypervisor));
GCLowLatency = new ReactiveObject<bool>();
GCLowLatency.LogChangesToValue(nameof(GCLowLatency));
} }
} }

View File

@@ -87,6 +87,7 @@ namespace Ryujinx.Ava.Systems.Configuration
IgnoreApplet = System.IgnoreControllerApplet, IgnoreApplet = System.IgnoreControllerApplet,
SkipUserProfiles = System.SkipUserProfilesManager, SkipUserProfiles = System.SkipUserProfilesManager,
UseHypervisor = System.UseHypervisor, UseHypervisor = System.UseHypervisor,
GCLowLatency = System.GCLowLatency,
GuiColumns = new GuiColumns GuiColumns = new GuiColumns
{ {
FavColumn = UI.GuiColumns.FavColumn, FavColumn = UI.GuiColumns.FavColumn,

View File

@@ -1070,6 +1070,23 @@ namespace Ryujinx.Ava.Systems.PlayReport
_ => FormattedValue.ForceReset _ => FormattedValue.ForceReset
}; };
private static FormattedValue TomodachiLifeLTD_Status(SingleValue value)
{
MessagePackObject messagePackObject = value.Matched.PackedValue;
MessagePackObjectDictionary messagePackObjectDictionary = messagePackObject.AsDictionary();
int miiCount = messagePackObjectDictionary["MiiNum"].AsInt32();
int fountainLevel = messagePackObjectDictionary["FountainLevel"].AsInt32();
return $"Looking after {"Mii".ToQuantity(miiCount)}, with an island level of {fountainLevel}";
}
private static FormattedValue AnimalCrossingNewHorizons_AppCommon(SingleValue value)
{
MessagePackObject messagePackObject = value.Matched.PackedValue;
MessagePackObjectDictionary messagePackObjectDictionary = messagePackObject.AsDictionary();
return $"Living on {messagePackObjectDictionary["LandName"].AsString()} Island";
}
} }
} }

View File

@@ -119,6 +119,19 @@ namespace Ryujinx.Ava.Systems.PlayReport
"based on what game you first launch.\n\nNSO emulators do not print any Play Report information past the first game launch so it's all we got.") "based on what game you first launch.\n\nNSO emulators do not print any Play Report information past the first game launch so it's all we got.")
.AddValueFormatter("launch_title_id", NsoEmulator_LaunchedGame) .AddValueFormatter("launch_title_id", NsoEmulator_LaunchedGame)
) )
.AddSpec(
[ "010051f0207b2000", "0100ca502552a000" ], // Tomodachi Life: Living the Dream + Demo
spec => spec
.WithDescription(
"based on your total Mii count and island level.")
.AddValueFormatter("Common", TomodachiLifeLTD_Status)
)
.AddSpec(
"01006f8002326000", // Animal Crossing New Horizons
spec => spec
.WithDescription("based on your island name.")
.AddValueFormatter("AppCmn", AnimalCrossingNewHorizons_AppCommon)
)
); );
private static string Playing(string game) => $"Playing {game}"; private static string Playing(string game) => $"Playing {game}";

View File

@@ -0,0 +1,30 @@
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Ava.UI.Helpers
{
public class LinuxSDLInterop
{
// TODO: add a parameter for prompt style
// TODO: look into adding text for the button
// TODO: check success of prompt box
public static int SimpleMessageBox(string caption, string text)
{
const string sdl = "SDL2";
[DllImport(sdl)]
static extern int SDL_Init(uint flags);
[DllImport(sdl, CallingConvention = CallingConvention.Cdecl)]
static extern int SDL_ShowSimpleMessageBox(uint flags, string title, string message, IntPtr window);
[DllImport(sdl)]
static extern void SDL_Quit();
SDL_Init(0);
SDL_ShowSimpleMessageBox(32 /* 32 = warning style */, caption, text, IntPtr.Zero);
SDL_Quit();
return 0;
}
}
}

View File

@@ -0,0 +1,62 @@
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Ava.UI.Helpers
{
public class macOSNativeInterop
{
// TODO: add a parameter for prompt style
// TODO: check success of prompt box
public static int SimpleMessageBox(string caption, string text, string button)
{
// Grab what we need to make the message box.
const string ObjCRuntime = "/usr/lib/libobjc.A.dylib";
const string FoundationFramework = "/System/Library/Frameworks/Foundation.framework/Foundation";
const string AppKitFramework = "/System/Library/Frameworks/AppKit.framework/AppKit";
[DllImport(ObjCRuntime, EntryPoint = "sel_registerName")]
static extern IntPtr GetSelector(string name);
[DllImport(ObjCRuntime, EntryPoint = "objc_getClass")]
static extern IntPtr GetClass(string name);
[DllImport(FoundationFramework, EntryPoint = "objc_msgSend")]
static extern IntPtr SendMessage(IntPtr target, IntPtr selector);
[DllImport(FoundationFramework, EntryPoint = "objc_msgSend")]
static extern IntPtr SendMessageWithParameter(IntPtr target, IntPtr selector, IntPtr param);
[DllImport(ObjCRuntime)]
static extern IntPtr dlopen(string path, int mode);
dlopen(AppKitFramework, 0x1); // have to invoke AppKit so that NSAlert doesn't return a null pointer
IntPtr NSStringClass = GetClass("NSString");
IntPtr Selector = GetSelector("stringWithUTF8String:");
IntPtr SharedApp = SendMessage(GetClass("NSApplication"), GetSelector("sharedApplication"));
IntPtr NSAlert = SendMessage(GetClass("NSAlert"), GetSelector("alloc"));
IntPtr AlertInstance = SendMessage(NSAlert, GetSelector("init"));
// Create caption, text, and button text.
IntPtr boxCaption = SendMessageWithParameter(NSStringClass, Selector, Marshal.StringToHGlobalAnsi(caption));
IntPtr boxText = SendMessageWithParameter(NSStringClass, Selector, Marshal.StringToHGlobalAnsi(text));
IntPtr boxButton = SendMessageWithParameter(NSStringClass, Selector, Marshal.StringToHGlobalAnsi(button));
// Set up the window.
SendMessageWithParameter(SharedApp, GetSelector("setActivationPolicy:"), IntPtr.Zero); // Give it a window.
SendMessageWithParameter(SharedApp, GetSelector("activateIgnoringOtherApps:"), (IntPtr) 1); // Force it to the front.
// Set up the message box.
SendMessageWithParameter(AlertInstance, GetSelector("setAlertStyle:"), IntPtr.Zero); // Set style to warning.
SendMessageWithParameter(AlertInstance, GetSelector("setMessageText:"), boxCaption);
SendMessageWithParameter(AlertInstance, GetSelector("setInformativeText:"), boxText);
SendMessageWithParameter(AlertInstance, GetSelector("addButtonWithTitle:"), boxButton);
// Send prompt to user, then clean up.
SendMessage(AlertInstance, GetSelector("runModal"));
SendMessage(AlertInstance, GetSelector("release"));
return 0;
}
}
}

View File

@@ -2067,6 +2067,7 @@ namespace Ryujinx.Ava.UI.ViewModels
Win32NativeInterop.SetWindowLongPtrW(hwnd, Win32NativeInterop.GWL_STYLE, Win32NativeInterop.SetWindowLongPtrW(hwnd, Win32NativeInterop.GWL_STYLE,
unchecked((nint)(Win32NativeInterop.WS_POPUP | Win32NativeInterop.WS_VISIBLE))); unchecked((nint)(Win32NativeInterop.WS_POPUP | Win32NativeInterop.WS_VISIBLE)));
// TODO: why is this nullable
Avalonia.Platform.Screen? screen = Window.Screens.ScreenFromVisual(Window); Avalonia.Platform.Screen? screen = Window.Screens.ScreenFromVisual(Window);
int w = screen?.Bounds.Width ?? 0; int w = screen?.Bounds.Width ?? 0;
int h = screen?.Bounds.Height ?? 0; int h = screen?.Bounds.Height ?? 0;

View File

@@ -286,6 +286,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool IsVulkanSelected => public bool IsVulkanSelected =>
GraphicsBackendIndex == 1 || (GraphicsBackendIndex == 0 && !OperatingSystem.IsMacOS()); GraphicsBackendIndex == 1 || (GraphicsBackendIndex == 0 && !OperatingSystem.IsMacOS());
public bool UseHypervisor { get; set; } public bool UseHypervisor { get; set; }
public bool GCLowLatency { get; set; }
public bool DisableP2P { get; set; } public bool DisableP2P { get; set; }
public bool ShowDirtyHacks => ConfigurationState.Instance.Hacks.ShowDirtyHacks; public bool ShowDirtyHacks => ConfigurationState.Instance.Hacks.ShowDirtyHacks;
@@ -689,6 +690,7 @@ namespace Ryujinx.Ava.UI.ViewModels
EnableLowPowerPptc = config.System.EnableLowPowerPtc; EnableLowPowerPptc = config.System.EnableLowPowerPtc;
MemoryMode = (int)config.System.MemoryManagerMode.Value; MemoryMode = (int)config.System.MemoryManagerMode.Value;
UseHypervisor = config.System.UseHypervisor; UseHypervisor = config.System.UseHypervisor;
GCLowLatency = config.System.GCLowLatency;
TurboMultiplier = config.System.TickScalar; TurboMultiplier = config.System.TickScalar;
// Graphics // Graphics
@@ -800,6 +802,7 @@ namespace Ryujinx.Ava.UI.ViewModels
config.System.EnableLowPowerPtc.Value = EnableLowPowerPptc; config.System.EnableLowPowerPtc.Value = EnableLowPowerPptc;
config.System.MemoryManagerMode.Value = (MemoryManagerMode)MemoryMode; config.System.MemoryManagerMode.Value = (MemoryManagerMode)MemoryMode;
config.System.UseHypervisor.Value = UseHypervisor; config.System.UseHypervisor.Value = UseHypervisor;
config.System.GCLowLatency.Value = GCLowLatency;
config.System.TickScalar.Value = TurboMultiplier; config.System.TickScalar.Value = TurboMultiplier;
// Graphics // Graphics

View File

@@ -71,6 +71,11 @@
<TextBlock Text="{ext:Locale SettingsTabSystemUseHypervisor}" <TextBlock Text="{ext:Locale SettingsTabSystemUseHypervisor}"
ToolTip.Tip="{ext:Locale UseHypervisorTooltip}" /> ToolTip.Tip="{ext:Locale UseHypervisorTooltip}" />
</CheckBox> </CheckBox>
<CheckBox
IsChecked="{Binding GCLowLatency}"
ToolTip.Tip="{ext:Locale GCLowLatencyTooltip}">
<TextBlock Text="{ext:Locale SettingsTabSystemGCLowLatency}" />
</CheckBox>
</StackPanel> </StackPanel>
<Separator Height="1" /> <Separator Height="1" />
<StackPanel <StackPanel

View File

@@ -0,0 +1,11 @@
{
"configProperties": {
"System.GC.Concurrent": true,
"System.GC.Server": false,
"System.GC.RetainVM": true,
"System.Runtime.TieredCompilation.QuickJit": false,
"System.Runtime.TieredCompilation.QuickJitForLoops": false,
"DOTNET_ReadyToRun": false,
"DOTNET_TieredPGO": true
}
}