Skip to content

Commit

Permalink
Add LinuxHelper to detect and increase vm.max_map_count
Browse files Browse the repository at this point in the history
With GUI dialogs, this should be a bit more user-friendly.
  • Loading branch information
TSRBerry committed Apr 25, 2023
1 parent 0624c85 commit c6f233d
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 6 deletions.
9 changes: 8 additions & 1 deletion Ryujinx.Ava/Assets/Locales/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@
"GameListContextMenuExtractDataLogoToolTip": "Extract the Logo section from Application's current config (including updates)",
"StatusBarGamesLoaded": "{0}/{1} Games Loaded",
"StatusBarSystemVersion": "System Version: {0}",
"LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected",
"LinuxVmMaxMapCountDialogTextPrimary": "Would you like to increase the value of vm.max_map_count to {0}",
"LinuxVmMaxMapCountDialogTextSecondary": "Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.",
"LinuxVmMaxMapCountDialogButtonUntilRestart": "Yes, until the next restart",
"LinuxVmMaxMapCountDialogButtonPersistent": "Yes, permanently",
"LinuxVmMaxMapCountWarningTextPrimary": "Max amount of memory mappings is lower than recommended.",
"LinuxVmMaxMapCountWarningTextSecondary": "The current value of vm.max_map_count ({0}) is lower than {1}. Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.\n\nYou might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that.",
"Settings": "Settings",
"SettingsTabGeneral": "User Interface",
"SettingsTabGeneralGeneral": "General",
Expand Down Expand Up @@ -642,4 +649,4 @@
"SettingsTabNetworkInterface": "Network Interface:",
"NetworkInterfaceTooltip": "The network interface used for LAN features",
"NetworkInterfaceDefault": "Default"
}
}
7 changes: 3 additions & 4 deletions Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common.Logging;
using System;
Expand All @@ -19,7 +18,7 @@ public static class ContentDialogHelper
{
private static bool _isChoiceDialogOpen;

public async static Task<UserResult> ShowContentDialog(
private async static Task<UserResult> ShowContentDialog(
string title,
object content,
string primaryButton,
Expand Down Expand Up @@ -67,7 +66,7 @@ public static class ContentDialogHelper
return result;
}

private async static Task<UserResult> ShowTextDialog(
public async static Task<UserResult> ShowTextDialog(
string title,
string primaryText,
string secondaryText,
Expand Down Expand Up @@ -319,7 +318,7 @@ public static async Task<ContentDialogResult> ShowAsync(ContentDialog contentDia

Window parent = GetMainWindow();

if (parent != null && parent.IsActive && parent is MainWindow window && window.ViewModel.IsGameRunning)
if (parent is { IsActive: true } and MainWindow window && window.ViewModel.IsGameRunning)
{
contentDialogOverlayWindow = new()
{
Expand Down
58 changes: 57 additions & 1 deletion Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
using System;
using System.ComponentModel;
using System.IO;
using System.Runtime.Versioning;
using System.Threading.Tasks;
using InputManager = Ryujinx.Input.HLE.InputManager;

Expand Down Expand Up @@ -256,7 +257,48 @@ private void Initialize()
ApplicationHelper.Initialize(VirtualFileSystem, AccountManager, LibHacHorizonManager.RyujinxClient, this);
}

protected void CheckLaunchState()
[SupportedOSPlatform("linux")]
private static async void ShowVmMaxMapCountWarning()
{
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LinuxVmMaxMapCountWarningTextSecondary,
LinuxHelper.VmMaxMapCount, LinuxHelper.RecommendedVmMaxMapCount);

await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountWarningTextPrimary],
LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountWarningTextSecondary]
);
}

[SupportedOSPlatform("linux")]
private static async void ShowVmMaxMapCountDialog()
{
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LinuxVmMaxMapCountDialogTextPrimary,
LinuxHelper.RecommendedVmMaxMapCount);

UserResult response = await ContentDialogHelper.ShowTextDialog(
$"Ryujinx - {LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogTitle]}",
LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogTextPrimary],
LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogTextSecondary],
LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogButtonUntilRestart],
LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogButtonPersistent],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
(int)Symbol.Help
);

switch (response)
{
case UserResult.Ok:
LinuxHelper.RunPkExec($"echo {LinuxHelper.RecommendedVmMaxMapCount} > {LinuxHelper.VmMaxMapCountPath}");
Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount} until the next restart");
break;
case UserResult.No:
LinuxHelper.RunPkExec($"echo \"vm.max_map_count = {LinuxHelper.RecommendedVmMaxMapCount}\" > {LinuxHelper.SysCtlConfigPath} && sysctl -p {LinuxHelper.SysCtlConfigPath}");
Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount}. Written to config: {LinuxHelper.SysCtlConfigPath}");
break;
}
}

private void CheckLaunchState()
{
if (ShowKeyErrorOnLoad)
{
Expand All @@ -266,6 +308,20 @@ protected void CheckLaunchState()
UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys, this));
}

if (OperatingSystem.IsLinux() && LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount)
{
Logger.Warning?.Print(LogClass.Application, $"The value of vm.max_map_count is lower than {LinuxHelper.RecommendedVmMaxMapCount}. ({LinuxHelper.VmMaxMapCount})");

if (LinuxHelper.PkExecPath is not null)
{
Dispatcher.UIThread.Post(ShowVmMaxMapCountDialog);
}
else
{
Dispatcher.UIThread.Post(ShowVmMaxMapCountWarning);
}
}

if (_deferLoad)
{
_deferLoad = false;
Expand Down
62 changes: 62 additions & 0 deletions Ryujinx.Ui.Common/Helper/LinuxHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.Versioning;

namespace Ryujinx.Ui.Common.Helper
{
[SupportedOSPlatform("linux")]
public static class LinuxHelper
{
// NOTE: This value was determined by manual tests and might need to be increased again.
public const int RecommendedVmMaxMapCount = 524288;
public const string VmMaxMapCountPath = "/proc/sys/vm/max_map_count";
public const string SysCtlConfigPath = "/etc/sysctl.d/99-Ryujinx.conf";
public static int VmMaxMapCount => int.Parse(File.ReadAllText(VmMaxMapCountPath));
public static string PkExecPath { get; } = GetBinaryPath("pkexec");

private static string GetBinaryPath(string binary)
{
string pathVar = Environment.GetEnvironmentVariable("PATH");

if (pathVar is null || string.IsNullOrEmpty(binary))
{
return null;
}

foreach (var searchPath in pathVar.Split(":", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries))
{
string binaryPath = Path.Combine(searchPath, binary);

if (File.Exists(binaryPath))
{
return binaryPath;
}
}

return null;
}

public static int RunPkExec(string command)
{
if (PkExecPath == null)
{
return 1;
}

using Process process = new()
{
StartInfo =
{
FileName = PkExecPath,
Arguments = $"sh -c '{command}'"
}
};

process.Start();
process.WaitForExit();

return process.ExitCode;
}
}
}
46 changes: 46 additions & 0 deletions Ryujinx/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,52 @@ static void Main(string[] args)
XInitThreads();
Environment.SetEnvironmentVariable("GDK_BACKEND", "x11");
setenv("GDK_BACKEND", "x11", 1);

int currentVmMaxMapCount = LinuxHelper.VmMaxMapCount;

if (LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount)
{
Logger.Warning?.Print(LogClass.Application, $"The value of vm.max_map_count is lower than {LinuxHelper.RecommendedVmMaxMapCount}. ({currentVmMaxMapCount})");

if (LinuxHelper.PkExecPath is not null)
{
var buttonTexts = new Dictionary<int, string>()
{
{ 0, "Yes, until the next restart" },
{ 1, "Yes, permanently" },
{ 2, "No" }
};

ResponseType response = GtkDialog.CreateCustomDialog(
"Ryujinx - Low limit for memory mappings detected",
$"Would you like to increase the value of vm.max_map_count to {LinuxHelper.RecommendedVmMaxMapCount}?",
"Some games might try to create more memory mappings than currently allowed. " +
"Ryujinx will crash as soon as this limit gets exceeded.",
buttonTexts,
MessageType.Question);

switch ((int)response)
{
case 0:
LinuxHelper.RunPkExec($"echo {LinuxHelper.RecommendedVmMaxMapCount} > {LinuxHelper.VmMaxMapCountPath}");
Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount} until the next restart");
break;
case 1:
LinuxHelper.RunPkExec($"echo \"vm.max_map_count = {LinuxHelper.RecommendedVmMaxMapCount}\" > {LinuxHelper.SysCtlConfigPath} && sysctl -p {LinuxHelper.SysCtlConfigPath}");
Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount}. Written to config: {LinuxHelper.SysCtlConfigPath}");
break;
}
}
else
{
GtkDialog.CreateWarningDialog(
"Max amount of memory mappings is lower than recommended.",
$"The current value of vm.max_map_count ({currentVmMaxMapCount}) is lower than {LinuxHelper.RecommendedVmMaxMapCount}." +
"Some games might try to create more memory mappings than currently allowed. " +
"Ryujinx will crash as soon as this limit gets exceeded.\n\n" +
"You might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that.");
}
}
}

if (OperatingSystem.IsMacOS())
Expand Down

0 comments on commit c6f233d

Please sign in to comment.