[GUI] Add network interface dropdown (#4597)

* Add network adapter dropdown from LDN build

* Ava: Add NetworkInterfaces to SettingsNetworkTab

* Add headless network interface option

* Add network interface dropdown to Avalonia

* Fix handling network interfaces without a gateway address

* gtk: Actually save selected network interface to config

* Increment config version
This commit is contained in:
TSRBerry 2023-04-16 17:25:20 +02:00 committed by GitHub
parent 40e87c634e
commit 69b6ef7a4a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 385 additions and 90 deletions

View file

@ -177,6 +177,8 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.Graphics.ScalingFilter.Event += UpdateScalingFilter; ConfigurationState.Instance.Graphics.ScalingFilter.Event += UpdateScalingFilter;
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel; ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel;
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState;
_gpuCancellationTokenSource = new CancellationTokenSource(); _gpuCancellationTokenSource = new CancellationTokenSource();
} }
@ -383,6 +385,11 @@ namespace Ryujinx.Ava
}); });
} }
private void UpdateLanInterfaceIdState(object sender, ReactiveEventArgs<string> e)
{
Device.Configuration.MultiplayerLanInterfaceId = e.NewValue;
}
public void Stop() public void Stop()
{ {
_isActive = false; _isActive = false;
@ -739,7 +746,8 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.System.IgnoreMissingServices, ConfigurationState.Instance.System.IgnoreMissingServices,
ConfigurationState.Instance.Graphics.AspectRatio, ConfigurationState.Instance.Graphics.AspectRatio,
ConfigurationState.Instance.System.AudioVolume, ConfigurationState.Instance.System.AudioVolume,
ConfigurationState.Instance.System.UseHypervisor); ConfigurationState.Instance.System.UseHypervisor,
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value);
Device = new Switch(configuration); Device = new Switch(configuration);
} }

View file

@ -638,5 +638,8 @@
"SmaaHigh": "SMAA High", "SmaaHigh": "SMAA High",
"SmaaUltra": "SMAA Ultra", "SmaaUltra": "SMAA Ultra",
"UserEditorTitle" : "Edit User", "UserEditorTitle" : "Edit User",
"UserEditorTitleCreate" : "Create User" "UserEditorTitleCreate" : "Create User",
"SettingsTabNetworkInterface": "Network Interface:",
"NetworkInterfaceTooltip": "The network interface used for LAN features",
"NetworkInterfaceDefault": "Default"
} }

View file

@ -23,6 +23,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Net.NetworkInformation;
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone; using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
namespace Ryujinx.Ava.UI.ViewModels namespace Ryujinx.Ava.UI.ViewModels
@ -35,6 +36,8 @@ namespace Ryujinx.Ava.UI.ViewModels
private readonly List<string> _validTzRegions; private readonly List<string> _validTzRegions;
private readonly Dictionary<string, string> _networkInterfaces;
private float _customResolutionScale; private float _customResolutionScale;
private int _resolutionScale; private int _resolutionScale;
private int _graphicsBackendMultithreadingIndex; private int _graphicsBackendMultithreadingIndex;
@ -50,6 +53,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public event Action CloseWindow; public event Action CloseWindow;
public event Action SaveSettingsEvent; public event Action SaveSettingsEvent;
private int _networkInterfaceIndex;
public int ResolutionScale public int ResolutionScale
{ {
@ -240,6 +244,11 @@ namespace Ryujinx.Ava.UI.ViewModels
public AvaloniaList<string> GameDirectories { get; set; } public AvaloniaList<string> GameDirectories { get; set; }
public ObservableCollection<ComboBoxItem> AvailableGpus { get; set; } public ObservableCollection<ComboBoxItem> AvailableGpus { get; set; }
public AvaloniaList<string> NetworkInterfaceList
{
get => new AvaloniaList<string>(_networkInterfaces.Keys);
}
public KeyboardHotkeys KeyboardHotkeys public KeyboardHotkeys KeyboardHotkeys
{ {
get => _keyboardHotkeys; get => _keyboardHotkeys;
@ -251,6 +260,16 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
public int NetworkInterfaceIndex
{
get => _networkInterfaceIndex;
set
{
_networkInterfaceIndex = value != -1 ? value : 0;
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[_networkInterfaceIndex]];
}
}
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this() public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
{ {
_virtualFileSystem = virtualFileSystem; _virtualFileSystem = virtualFileSystem;
@ -267,8 +286,10 @@ namespace Ryujinx.Ava.UI.ViewModels
TimeZones = new AvaloniaList<TimeZone>(); TimeZones = new AvaloniaList<TimeZone>();
AvailableGpus = new ObservableCollection<ComboBoxItem>(); AvailableGpus = new ObservableCollection<ComboBoxItem>();
_validTzRegions = new List<string>(); _validTzRegions = new List<string>();
_networkInterfaces = new Dictionary<string, string>();
CheckSoundBackends(); CheckSoundBackends();
PopulateNetworkInterfaces();
if (Program.PreviewerDetached) if (Program.PreviewerDetached)
{ {
@ -327,6 +348,17 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
private void PopulateNetworkInterfaces()
{
_networkInterfaces.Clear();
_networkInterfaces.Add(LocaleManager.Instance[LocaleKeys.NetworkInterfaceDefault], "0");
foreach (NetworkInterface networkInterface in NetworkInterface.GetAllNetworkInterfaces())
{
_networkInterfaces.Add(networkInterface.Name, networkInterface.Id);
}
}
public void ValidateAndSetTimeZone(string location) public void ValidateAndSetTimeZone(string location)
{ {
if (_validTzRegions.Contains(location)) if (_validTzRegions.Contains(location))
@ -414,6 +446,8 @@ namespace Ryujinx.Ava.UI.ViewModels
EnableFsAccessLog = config.Logger.EnableFsAccessLog; EnableFsAccessLog = config.Logger.EnableFsAccessLog;
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode; FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value; OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
NetworkInterfaceIndex = _networkInterfaces.Values.ToList().IndexOf(config.Multiplayer.LanInterfaceId.Value);
} }
public void SaveSettings() public void SaveSettings()
@ -515,6 +549,8 @@ namespace Ryujinx.Ava.UI.ViewModels
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode; config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel; config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
config.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[NetworkInterfaceIndex]];
config.ToFileFormat().SaveConfig(Program.ConfigurationPath); config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
MainWindow.UpdateGraphicsConfig(); MainWindow.UpdateGraphicsConfig();

View file

@ -1,4 +1,4 @@
<UserControl <UserControl
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsNetworkView" x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsNetworkView"
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@ -29,7 +29,18 @@
<TextBlock Text="{locale:Locale SettingsTabSystemEnableInternetAccess}" <TextBlock Text="{locale:Locale SettingsTabSystemEnableInternetAccess}"
ToolTip.Tip="{locale:Locale EnableInternetAccessTooltip}" /> ToolTip.Tip="{locale:Locale EnableInternetAccessTooltip}" />
</CheckBox> </CheckBox>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabNetworkInterface}"
ToolTip.Tip="{locale:Locale NetworkInterfaceTooltip}"
Width="200" />
<ComboBox SelectedIndex="{Binding NetworkInterfaceIndex}"
ToolTip.Tip="{locale:Locale NetworkInterfaceTooltip}"
HorizontalContentAlignment="Left"
Items="{Binding NetworkInterfaceList}"
Width="250" />
</StackPanel>
</StackPanel> </StackPanel>
</Border> </Border>
</ScrollViewer> </ScrollViewer>
</UserControl> </UserControl>

View file

@ -0,0 +1,66 @@
using System.Net.NetworkInformation;
namespace Ryujinx.Common.Utilities
{
public static class NetworkHelpers
{
private static (IPInterfaceProperties, UnicastIPAddressInformation) GetLocalInterface(NetworkInterface adapter, bool isPreferred)
{
IPInterfaceProperties properties = adapter.GetIPProperties();
if (isPreferred || (properties.GatewayAddresses.Count > 0 && properties.DnsAddresses.Count > 0))
{
foreach (UnicastIPAddressInformation info in properties.UnicastAddresses)
{
// Only accept an IPv4 address
if (info.Address.GetAddressBytes().Length == 4)
{
return (properties, info);
}
}
}
return (null, null);
}
public static (IPInterfaceProperties, UnicastIPAddressInformation) GetLocalInterface(string lanInterfaceId = "0")
{
if (!NetworkInterface.GetIsNetworkAvailable())
{
return (null, null);
}
IPInterfaceProperties targetProperties = null;
UnicastIPAddressInformation targetAddressInfo = null;
NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces();
string guid = lanInterfaceId;
bool hasPreference = guid != "0";
foreach (NetworkInterface adapter in interfaces)
{
bool isPreferred = adapter.Id == guid;
// Ignore loopback and non IPv4 capable interface.
if (isPreferred || (targetProperties == null && adapter.NetworkInterfaceType != NetworkInterfaceType.Loopback && adapter.Supports(NetworkInterfaceComponent.IPv4)))
{
(IPInterfaceProperties properties, UnicastIPAddressInformation info) = GetLocalInterface(adapter, isPreferred);
if (properties != null)
{
targetProperties = properties;
targetAddressInfo = info;
if (isPreferred || !hasPreference)
{
break;
}
}
}
}
return (targetProperties, targetAddressInfo);
}
}
}

View file

@ -153,6 +153,11 @@ namespace Ryujinx.HLE
/// </summary> /// </summary>
internal readonly bool UseHypervisor; internal readonly bool UseHypervisor;
/// <summary>
/// Multiplayer LAN Interface ID (device GUID)
/// </summary>
public string MultiplayerLanInterfaceId { internal get; set; }
/// <summary> /// <summary>
/// An action called when HLE force a refresh of output after docked mode changed. /// An action called when HLE force a refresh of output after docked mode changed.
/// </summary> /// </summary>
@ -181,32 +186,34 @@ namespace Ryujinx.HLE
bool ignoreMissingServices, bool ignoreMissingServices,
AspectRatio aspectRatio, AspectRatio aspectRatio,
float audioVolume, float audioVolume,
bool useHypervisor) bool useHypervisor,
string multiplayerLanInterfaceId)
{ {
VirtualFileSystem = virtualFileSystem; VirtualFileSystem = virtualFileSystem;
LibHacHorizonManager = libHacHorizonManager; LibHacHorizonManager = libHacHorizonManager;
AccountManager = accountManager; AccountManager = accountManager;
ContentManager = contentManager; ContentManager = contentManager;
UserChannelPersistence = userChannelPersistence; UserChannelPersistence = userChannelPersistence;
GpuRenderer = gpuRenderer; GpuRenderer = gpuRenderer;
AudioDeviceDriver = audioDeviceDriver; AudioDeviceDriver = audioDeviceDriver;
MemoryConfiguration = memoryConfiguration; MemoryConfiguration = memoryConfiguration;
HostUiHandler = hostUiHandler; HostUiHandler = hostUiHandler;
SystemLanguage = systemLanguage; SystemLanguage = systemLanguage;
Region = region; Region = region;
EnableVsync = enableVsync; EnableVsync = enableVsync;
EnableDockedMode = enableDockedMode; EnableDockedMode = enableDockedMode;
EnablePtc = enablePtc; EnablePtc = enablePtc;
EnableInternetAccess = enableInternetAccess; EnableInternetAccess = enableInternetAccess;
FsIntegrityCheckLevel = fsIntegrityCheckLevel; FsIntegrityCheckLevel = fsIntegrityCheckLevel;
FsGlobalAccessLogMode = fsGlobalAccessLogMode; FsGlobalAccessLogMode = fsGlobalAccessLogMode;
SystemTimeOffset = systemTimeOffset; SystemTimeOffset = systemTimeOffset;
TimeZone = timeZone; TimeZone = timeZone;
MemoryManagerMode = memoryManagerMode; MemoryManagerMode = memoryManagerMode;
IgnoreMissingServices = ignoreMissingServices; IgnoreMissingServices = ignoreMissingServices;
AspectRatio = aspectRatio; AspectRatio = aspectRatio;
AudioVolume = audioVolume; AudioVolume = audioVolume;
UseHypervisor = useHypervisor; UseHypervisor = useHypervisor;
MultiplayerLanInterfaceId = multiplayerLanInterfaceId;
} }
} }
} }

View file

@ -6,7 +6,6 @@ using Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types;
using System; using System;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text;
namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
{ {
@ -16,6 +15,7 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
private IPInterfaceProperties _targetPropertiesCache = null; private IPInterfaceProperties _targetPropertiesCache = null;
private UnicastIPAddressInformation _targetAddressInfoCache = null; private UnicastIPAddressInformation _targetAddressInfoCache = null;
private string _cacheChosenInterface = null;
public IGeneralService() public IGeneralService()
{ {
@ -65,7 +65,7 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
{ {
ulong networkProfileDataPosition = context.Request.RecvListBuff[0].Position; ulong networkProfileDataPosition = context.Request.RecvListBuff[0].Position;
(IPInterfaceProperties interfaceProperties, UnicastIPAddressInformation unicastAddress) = GetLocalInterface(); (IPInterfaceProperties interfaceProperties, UnicastIPAddressInformation unicastAddress) = GetLocalInterface(context);
if (interfaceProperties == null || unicastAddress == null) if (interfaceProperties == null || unicastAddress == null)
{ {
@ -95,7 +95,7 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
// GetCurrentIpAddress() -> nn::nifm::IpV4Address // GetCurrentIpAddress() -> nn::nifm::IpV4Address
public ResultCode GetCurrentIpAddress(ServiceCtx context) public ResultCode GetCurrentIpAddress(ServiceCtx context)
{ {
(_, UnicastIPAddressInformation unicastAddress) = GetLocalInterface(); (_, UnicastIPAddressInformation unicastAddress) = GetLocalInterface(context);
if (unicastAddress == null) if (unicastAddress == null)
{ {
@ -113,7 +113,7 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
// GetCurrentIpConfigInfo() -> (nn::nifm::IpAddressSetting, nn::nifm::DnsSetting) // GetCurrentIpConfigInfo() -> (nn::nifm::IpAddressSetting, nn::nifm::DnsSetting)
public ResultCode GetCurrentIpConfigInfo(ServiceCtx context) public ResultCode GetCurrentIpConfigInfo(ServiceCtx context)
{ {
(IPInterfaceProperties interfaceProperties, UnicastIPAddressInformation unicastAddress) = GetLocalInterface(); (IPInterfaceProperties interfaceProperties, UnicastIPAddressInformation unicastAddress) = GetLocalInterface(context);
if (interfaceProperties == null || unicastAddress == null) if (interfaceProperties == null || unicastAddress == null)
{ {
@ -163,51 +163,23 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
return ResultCode.Success; return ResultCode.Success;
} }
private (IPInterfaceProperties, UnicastIPAddressInformation) GetLocalInterface() private (IPInterfaceProperties, UnicastIPAddressInformation) GetLocalInterface(ServiceCtx context)
{ {
if (!NetworkInterface.GetIsNetworkAvailable()) if (!NetworkInterface.GetIsNetworkAvailable())
{ {
return (null, null); return (null, null);
} }
if (_targetPropertiesCache != null && _targetAddressInfoCache != null) string chosenInterface = context.Device.Configuration.MultiplayerLanInterfaceId;
if (_targetPropertiesCache == null || _targetAddressInfoCache == null || _cacheChosenInterface != chosenInterface)
{ {
return (_targetPropertiesCache, _targetAddressInfoCache); _cacheChosenInterface = chosenInterface;
(_targetPropertiesCache, _targetAddressInfoCache) = NetworkHelpers.GetLocalInterface(chosenInterface);
} }
IPInterfaceProperties targetProperties = null; return (_targetPropertiesCache, _targetAddressInfoCache);
UnicastIPAddressInformation targetAddressInfo = null;
NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces();
foreach (NetworkInterface adapter in interfaces)
{
// Ignore loopback and non IPv4 capable interface.
if (targetProperties == null && adapter.NetworkInterfaceType != NetworkInterfaceType.Loopback && adapter.Supports(NetworkInterfaceComponent.IPv4))
{
IPInterfaceProperties properties = adapter.GetIPProperties();
if (properties.GatewayAddresses.Count > 0 && properties.DnsAddresses.Count > 0)
{
foreach (UnicastIPAddressInformation info in properties.UnicastAddresses)
{
// Only accept an IPv4 address
if (info.Address.GetAddressBytes().Length == 4)
{
targetProperties = properties;
targetAddressInfo = info;
break;
}
}
}
}
}
_targetPropertiesCache = targetProperties;
_targetAddressInfoCache = targetAddressInfo;
return (targetProperties, targetAddressInfo);
} }
private void LocalInterfaceCacheHandler(object sender, EventArgs e) private void LocalInterfaceCacheHandler(object sender, EventArgs e)

View file

@ -18,7 +18,7 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types
IsDhcpEnabled = OperatingSystem.IsMacOS() || interfaceProperties.DhcpServerAddresses.Count != 0; IsDhcpEnabled = OperatingSystem.IsMacOS() || interfaceProperties.DhcpServerAddresses.Count != 0;
Address = new IpV4Address(unicastIPAddressInformation.Address); Address = new IpV4Address(unicastIPAddressInformation.Address);
IPv4Mask = new IpV4Address(unicastIPAddressInformation.IPv4Mask); IPv4Mask = new IpV4Address(unicastIPAddressInformation.IPv4Mask);
GatewayAddress = new IpV4Address(interfaceProperties.GatewayAddresses[0].Address); GatewayAddress = (interfaceProperties.GatewayAddresses.Count == 0) ? new IpV4Address() : new IpV4Address(interfaceProperties.GatewayAddresses[0].Address);
} }
} }
} }

View file

@ -132,6 +132,9 @@ namespace Ryujinx.Headless.SDL2
[Option("use-hypervisor", Required = false, Default = true, HelpText = "Uses Hypervisor over JIT if available.")] [Option("use-hypervisor", Required = false, Default = true, HelpText = "Uses Hypervisor over JIT if available.")]
public bool UseHypervisor { get; set; } public bool UseHypervisor { get; set; }
[Option("lan-interface-id", Required = false, Default = "0", HelpText = "GUID for the network interface used by LAN.")]
public string MultiplayerLanInterfaceId { get; set; }
// Logging // Logging
[Option("disable-file-logging", Required = false, Default = false, HelpText = "Disables logging to a file on disk.")] [Option("disable-file-logging", Required = false, Default = false, HelpText = "Disables logging to a file on disk.")]

View file

@ -548,7 +548,8 @@ namespace Ryujinx.Headless.SDL2
options.IgnoreMissingServices, options.IgnoreMissingServices,
options.AspectRatio, options.AspectRatio,
options.AudioVolume, options.AudioVolume,
options.UseHypervisor); options.UseHypervisor,
options.MultiplayerLanInterfaceId);
return new Switch(configuration); return new Switch(configuration);
} }

View file

@ -14,7 +14,7 @@ namespace Ryujinx.Ui.Common.Configuration
/// <summary> /// <summary>
/// The current version of the file format /// The current version of the file format
/// </summary> /// </summary>
public const int CurrentVersion = 45; public const int CurrentVersion = 46;
/// <summary> /// <summary>
/// Version of the configuration file format /// Version of the configuration file format
@ -350,6 +350,11 @@ namespace Ryujinx.Ui.Common.Configuration
/// </summary> /// </summary>
public string PreferredGpu { get; set; } public string PreferredGpu { get; set; }
/// <summary>
/// GUID for the network interface used by LAN (or 0 for default)
/// </summary>
public string MultiplayerLanInterfaceId { get; set; }
/// <summary> /// <summary>
/// Uses Hypervisor over JIT if available /// Uses Hypervisor over JIT if available
/// </summary> /// </summary>

View file

@ -517,6 +517,22 @@ namespace Ryujinx.Ui.Common.Configuration
} }
} }
/// <summary>
/// Multiplayer configuration section
/// </summary>
public class MultiplayerSection
{
/// <summary>
/// GUID for the network interface used by LAN (or 0 for default)
/// </summary>
public ReactiveObject<string> LanInterfaceId { get; private set; }
public MultiplayerSection()
{
LanInterfaceId = new ReactiveObject<string>();
}
}
/// <summary> /// <summary>
/// The default configuration instance /// The default configuration instance
/// </summary> /// </summary>
@ -547,6 +563,11 @@ namespace Ryujinx.Ui.Common.Configuration
/// </summary> /// </summary>
public HidSection Hid { get; private set; } public HidSection Hid { get; private set; }
/// <summary>
/// The Multiplayer section
/// </summary>
public MultiplayerSection Multiplayer { get; private set; }
/// <summary> /// <summary>
/// Enables or disables Discord Rich Presence /// Enables or disables Discord Rich Presence
/// </summary> /// </summary>
@ -574,6 +595,7 @@ namespace Ryujinx.Ui.Common.Configuration
System = new SystemSection(); System = new SystemSection();
Graphics = new GraphicsSection(); Graphics = new GraphicsSection();
Hid = new HidSection(); Hid = new HidSection();
Multiplayer = new MultiplayerSection();
EnableDiscordIntegration = new ReactiveObject<bool>(); EnableDiscordIntegration = new ReactiveObject<bool>();
CheckUpdatesOnStart = new ReactiveObject<bool>(); CheckUpdatesOnStart = new ReactiveObject<bool>();
ShowConfirmExit = new ReactiveObject<bool>(); ShowConfirmExit = new ReactiveObject<bool>();
@ -674,7 +696,8 @@ namespace Ryujinx.Ui.Common.Configuration
ControllerConfig = new List<JsonObject>(), ControllerConfig = new List<JsonObject>(),
InputConfig = Hid.InputConfig, InputConfig = Hid.InputConfig,
GraphicsBackend = Graphics.GraphicsBackend, GraphicsBackend = Graphics.GraphicsBackend,
PreferredGpu = Graphics.PreferredGpu PreferredGpu = Graphics.PreferredGpu,
MultiplayerLanInterfaceId = Multiplayer.LanInterfaceId
}; };
return configurationFile; return configurationFile;
@ -727,6 +750,7 @@ namespace Ryujinx.Ui.Common.Configuration
System.ExpandRam.Value = false; System.ExpandRam.Value = false;
System.IgnoreMissingServices.Value = false; System.IgnoreMissingServices.Value = false;
System.UseHypervisor.Value = true; System.UseHypervisor.Value = true;
Multiplayer.LanInterfaceId.Value = "0";
Ui.GuiColumns.FavColumn.Value = true; Ui.GuiColumns.FavColumn.Value = true;
Ui.GuiColumns.IconColumn.Value = true; Ui.GuiColumns.IconColumn.Value = true;
Ui.GuiColumns.AppColumn.Value = true; Ui.GuiColumns.AppColumn.Value = true;
@ -1308,6 +1332,15 @@ namespace Ryujinx.Ui.Common.Configuration
configurationFileUpdated = true; configurationFileUpdated = true;
} }
if (configurationFileFormat.Version < 46)
{
Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 45.");
configurationFileFormat.MultiplayerLanInterfaceId = "0";
configurationFileUpdated = true;
}
Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog; Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog;
Graphics.ResScale.Value = configurationFileFormat.ResScale; Graphics.ResScale.Value = configurationFileFormat.ResScale;
Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom; Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom;
@ -1366,12 +1399,12 @@ namespace Ryujinx.Ui.Common.Configuration
Ui.ColumnSort.SortColumnId.Value = configurationFileFormat.ColumnSort.SortColumnId; Ui.ColumnSort.SortColumnId.Value = configurationFileFormat.ColumnSort.SortColumnId;
Ui.ColumnSort.SortAscending.Value = configurationFileFormat.ColumnSort.SortAscending; Ui.ColumnSort.SortAscending.Value = configurationFileFormat.ColumnSort.SortAscending;
Ui.GameDirs.Value = configurationFileFormat.GameDirs; Ui.GameDirs.Value = configurationFileFormat.GameDirs;
Ui.ShownFileTypes.NSP.Value = configurationFileFormat.ShownFileTypes.NSP; Ui.ShownFileTypes.NSP.Value = configurationFileFormat.ShownFileTypes.NSP;
Ui.ShownFileTypes.PFS0.Value = configurationFileFormat.ShownFileTypes.PFS0; Ui.ShownFileTypes.PFS0.Value = configurationFileFormat.ShownFileTypes.PFS0;
Ui.ShownFileTypes.XCI.Value = configurationFileFormat.ShownFileTypes.XCI; Ui.ShownFileTypes.XCI.Value = configurationFileFormat.ShownFileTypes.XCI;
Ui.ShownFileTypes.NCA.Value = configurationFileFormat.ShownFileTypes.NCA; Ui.ShownFileTypes.NCA.Value = configurationFileFormat.ShownFileTypes.NCA;
Ui.ShownFileTypes.NRO.Value = configurationFileFormat.ShownFileTypes.NRO; Ui.ShownFileTypes.NRO.Value = configurationFileFormat.ShownFileTypes.NRO;
Ui.ShownFileTypes.NSO.Value = configurationFileFormat.ShownFileTypes.NSO; Ui.ShownFileTypes.NSO.Value = configurationFileFormat.ShownFileTypes.NSO;
Ui.EnableCustomTheme.Value = configurationFileFormat.EnableCustomTheme; Ui.EnableCustomTheme.Value = configurationFileFormat.EnableCustomTheme;
Ui.LanguageCode.Value = configurationFileFormat.LanguageCode; Ui.LanguageCode.Value = configurationFileFormat.LanguageCode;
Ui.CustomThemePath.Value = configurationFileFormat.CustomThemePath; Ui.CustomThemePath.Value = configurationFileFormat.CustomThemePath;
@ -1393,6 +1426,8 @@ namespace Ryujinx.Ui.Common.Configuration
Hid.InputConfig.Value = new List<InputConfig>(); Hid.InputConfig.Value = new List<InputConfig>();
} }
Multiplayer.LanInterfaceId.Value = configurationFileFormat.MultiplayerLanInterfaceId;
if (configurationFileUpdated) if (configurationFileUpdated)
{ {
ToFileFormat().SaveConfig(configurationFilePath); ToFileFormat().SaveConfig(configurationFilePath);
@ -1418,4 +1453,4 @@ namespace Ryujinx.Ui.Common.Configuration
Instance = new ConfigurationState(); Instance = new ConfigurationState();
} }
} }
} }

View file

@ -598,7 +598,8 @@ namespace Ryujinx.Ui
ConfigurationState.Instance.System.IgnoreMissingServices, ConfigurationState.Instance.System.IgnoreMissingServices,
ConfigurationState.Instance.Graphics.AspectRatio, ConfigurationState.Instance.Graphics.AspectRatio,
ConfigurationState.Instance.System.AudioVolume, ConfigurationState.Instance.System.AudioVolume,
ConfigurationState.Instance.System.UseHypervisor); ConfigurationState.Instance.System.UseHypervisor,
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value);
_emulationContext = new HLE.Switch(configuration); _emulationContext = new HLE.Switch(configuration);
} }

View file

@ -17,6 +17,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Net.NetworkInformation;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using GUI = Gtk.Builder.ObjectAttribute; using GUI = Gtk.Builder.ObjectAttribute;
@ -84,6 +85,7 @@ namespace Ryujinx.Ui.Windows
[GUI] Adjustment _systemTimeDaySpinAdjustment; [GUI] Adjustment _systemTimeDaySpinAdjustment;
[GUI] Adjustment _systemTimeHourSpinAdjustment; [GUI] Adjustment _systemTimeHourSpinAdjustment;
[GUI] Adjustment _systemTimeMinuteSpinAdjustment; [GUI] Adjustment _systemTimeMinuteSpinAdjustment;
[GUI] ComboBoxText _multiLanSelect;
[GUI] CheckButton _custThemeToggle; [GUI] CheckButton _custThemeToggle;
[GUI] Entry _custThemePath; [GUI] Entry _custThemePath;
[GUI] ToggleButton _browseThemePath; [GUI] ToggleButton _browseThemePath;
@ -348,6 +350,8 @@ namespace Ryujinx.Ui.Windows
UpdatePreferredGpuComboBox(); UpdatePreferredGpuComboBox();
_graphicsBackend.Changed += (sender, e) => UpdatePreferredGpuComboBox(); _graphicsBackend.Changed += (sender, e) => UpdatePreferredGpuComboBox();
PopulateNetworkInterfaces();
_multiLanSelect.SetActiveId(ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value);
_custThemePath.Buffer.Text = ConfigurationState.Instance.Ui.CustomThemePath; _custThemePath.Buffer.Text = ConfigurationState.Instance.Ui.CustomThemePath;
_resScaleText.Buffer.Text = ConfigurationState.Instance.Graphics.ResScaleCustom.Value.ToString(); _resScaleText.Buffer.Text = ConfigurationState.Instance.Graphics.ResScaleCustom.Value.ToString();
@ -490,6 +494,19 @@ namespace Ryujinx.Ui.Windows
} }
} }
private void PopulateNetworkInterfaces()
{
NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces();
foreach (NetworkInterface nif in interfaces)
{
string guid = nif.Id;
string name = nif.Name;
_multiLanSelect.Append(guid, name);
}
}
private void UpdateSystemTimeSpinners() private void UpdateSystemTimeSpinners()
{ {
//Bind system time events //Bind system time events
@ -616,6 +633,7 @@ namespace Ryujinx.Ui.Windows
ConfigurationState.Instance.Graphics.AntiAliasing.Value = Enum.Parse<AntiAliasing>(_antiAliasing.ActiveId); ConfigurationState.Instance.Graphics.AntiAliasing.Value = Enum.Parse<AntiAliasing>(_antiAliasing.ActiveId);
ConfigurationState.Instance.Graphics.ScalingFilter.Value = Enum.Parse<ScalingFilter>(_scalingFilter.ActiveId); ConfigurationState.Instance.Graphics.ScalingFilter.Value = Enum.Parse<ScalingFilter>(_scalingFilter.ActiveId);
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value = (int)_scalingFilterLevel.Value; ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value = (int)_scalingFilterLevel.Value;
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value = _multiLanSelect.ActiveId;
_previousVolumeLevel = ConfigurationState.Instance.System.AudioVolume.Value; _previousVolumeLevel = ConfigurationState.Instance.System.AudioVolume.Value;

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 --> <!-- Generated with glade 3.40.0 -->
<interface> <interface>
<requires lib="gtk+" version="3.20"/> <requires lib="gtk+" version="3.20"/>
<object class="GtkAdjustment" id="_fsLogSpinAdjustment"> <object class="GtkAdjustment" id="_fsLogSpinAdjustment">
@ -7,6 +7,12 @@
<property name="step-increment">1</property> <property name="step-increment">1</property>
<property name="page-increment">10</property> <property name="page-increment">10</property>
</object> </object>
<object class="GtkAdjustment" id="_scalingFilterLevel">
<property name="upper">101</property>
<property name="step-increment">1</property>
<property name="page-increment">5</property>
<property name="page-size">1</property>
</object>
<object class="GtkAdjustment" id="_systemTimeDaySpinAdjustment"> <object class="GtkAdjustment" id="_systemTimeDaySpinAdjustment">
<property name="lower">1</property> <property name="lower">1</property>
<property name="upper">31</property> <property name="upper">31</property>
@ -40,13 +46,6 @@
<property name="inline-completion">True</property> <property name="inline-completion">True</property>
<property name="inline-selection">True</property> <property name="inline-selection">True</property>
</object> </object>
<object class="GtkAdjustment" id="_scalingFilterLevel">
<property name="lower">0</property>
<property name="upper">101</property>
<property name="step-increment">1</property>
<property name="page-increment">5</property>
<property name="page-size">1</property>
</object>
<object class="GtkWindow" id="_settingsWin"> <object class="GtkWindow" id="_settingsWin">
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="title" translatable="yes">Ryujinx - Settings</property> <property name="title" translatable="yes">Ryujinx - Settings</property>
@ -2862,6 +2861,136 @@
<property name="tab-fill">False</property> <property name="tab-fill">False</property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkBox" id="TabMultiplayer">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-left">5</property>
<property name="margin-right">10</property>
<property name="margin-top">5</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="CatLAN">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="valign">start</property>
<property name="margin-left">5</property>
<property name="margin-right">5</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="margin-bottom">5</property>
<property name="label" translatable="yes">LAN Mode</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="LANOptions">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="valign">start</property>
<property name="margin-left">10</property>
<property name="margin-right">10</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="NetworkInterfaceBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">The network interface used for LAN features</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Network Interface:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="_multiLanSelect">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">The network interface used for LAN features</property>
<property name="active-id">0</property>
<items>
<item id="0" translatable="yes">Default</item>
</items>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="margin-bottom">5</property>
<property name="label" translatable="yes">To use LAN functionality in games, Enable Guest Internet Access must be checked in System.</property>
<property name="wrap">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">5</property>
</packing>
</child>
<child type="tab">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Multiplayer</property>
</object>
<packing>
<property name="position">5</property>
<property name="tab-fill">False</property>
</packing>
</child>
</object> </object>
</child> </child>
</object> </object>