فهرست منبع

[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
TSRBerry 3 سال پیش
والد
کامیت
69b6ef7a4a

+ 9 - 1
Ryujinx.Ava/AppHost.cs

@@ -177,6 +177,8 @@ namespace Ryujinx.Ava
             ConfigurationState.Instance.Graphics.ScalingFilter.Event       += UpdateScalingFilter;
             ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event  += UpdateScalingFilterLevel;
 
+            ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event   += UpdateLanInterfaceIdState;
+
             _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()
         {
             _isActive = false;
@@ -739,7 +746,8 @@ namespace Ryujinx.Ava
                                                      ConfigurationState.Instance.System.IgnoreMissingServices,
                                                      ConfigurationState.Instance.Graphics.AspectRatio,
                                                      ConfigurationState.Instance.System.AudioVolume,
-                                                     ConfigurationState.Instance.System.UseHypervisor);
+                                                     ConfigurationState.Instance.System.UseHypervisor,
+                                                     ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value);
 
             Device = new Switch(configuration);
         }

+ 4 - 1
Ryujinx.Ava/Assets/Locales/en_US.json

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

+ 36 - 0
Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs

@@ -23,6 +23,7 @@ using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.Linq;
 using System.Runtime.InteropServices;
+using System.Net.NetworkInformation;
 using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
 
 namespace Ryujinx.Ava.UI.ViewModels
@@ -35,6 +36,8 @@ namespace Ryujinx.Ava.UI.ViewModels
 
         private readonly List<string> _validTzRegions;
 
+        private readonly Dictionary<string, string> _networkInterfaces;
+
         private float _customResolutionScale;
         private int _resolutionScale;
         private int _graphicsBackendMultithreadingIndex;
@@ -50,6 +53,7 @@ namespace Ryujinx.Ava.UI.ViewModels
 
         public event Action CloseWindow;
         public event Action SaveSettingsEvent;
+        private int _networkInterfaceIndex;
 
         public int ResolutionScale
         {
@@ -240,6 +244,11 @@ namespace Ryujinx.Ava.UI.ViewModels
         public AvaloniaList<string> GameDirectories { get; set; }
         public ObservableCollection<ComboBoxItem> AvailableGpus { get; set; }
 
+        public AvaloniaList<string> NetworkInterfaceList
+        {
+            get => new AvaloniaList<string>(_networkInterfaces.Keys);
+        }
+
         public KeyboardHotkeys 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()
         {
             _virtualFileSystem = virtualFileSystem;
@@ -267,8 +286,10 @@ namespace Ryujinx.Ava.UI.ViewModels
             TimeZones = new AvaloniaList<TimeZone>();
             AvailableGpus = new ObservableCollection<ComboBoxItem>();
             _validTzRegions = new List<string>();
+            _networkInterfaces = new Dictionary<string, string>();
 
             CheckSoundBackends();
+            PopulateNetworkInterfaces();
 
             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)
         {
             if (_validTzRegions.Contains(location))
@@ -414,6 +446,8 @@ namespace Ryujinx.Ava.UI.ViewModels
             EnableFsAccessLog = config.Logger.EnableFsAccessLog;
             FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
             OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
+
+            NetworkInterfaceIndex = _networkInterfaces.Values.ToList().IndexOf(config.Multiplayer.LanInterfaceId.Value);
         }
 
         public void SaveSettings()
@@ -515,6 +549,8 @@ namespace Ryujinx.Ava.UI.ViewModels
             config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
             config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
 
+            config.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[NetworkInterfaceIndex]];
+
             config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
 
             MainWindow.UpdateGraphicsConfig();

+ 13 - 2
Ryujinx.Ava/UI/Views/Settings/SettingsNetworkView.axaml

@@ -1,4 +1,4 @@
-<UserControl 
+<UserControl
     x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsNetworkView"
     xmlns="https://github.com/avaloniaui"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -29,7 +29,18 @@
                     <TextBlock Text="{locale:Locale SettingsTabSystemEnableInternetAccess}"
                                ToolTip.Tip="{locale:Locale EnableInternetAccessTooltip}" />
                 </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>
         </Border>
     </ScrollViewer>
-</UserControl>
+</UserControl>

+ 66 - 0
Ryujinx.Common/Utilities/NetworkHelpers.cs

@@ -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);
+        }
+    }
+}

+ 32 - 25
Ryujinx.HLE/HLEConfiguration.cs

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

+ 11 - 39
Ryujinx.HLE/HOS/Services/Nifm/StaticService/IGeneralService.cs

@@ -6,7 +6,6 @@ using Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types;
 using System;
 using System.Net.NetworkInformation;
 using System.Runtime.CompilerServices;
-using System.Text;
 
 namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
 {
@@ -16,6 +15,7 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
 
         private IPInterfaceProperties _targetPropertiesCache = null;
         private UnicastIPAddressInformation _targetAddressInfoCache = null;
+        private string _cacheChosenInterface = null;
 
         public IGeneralService()
         {
@@ -65,7 +65,7 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
         {
             ulong networkProfileDataPosition = context.Request.RecvListBuff[0].Position;
 
-            (IPInterfaceProperties interfaceProperties, UnicastIPAddressInformation unicastAddress) = GetLocalInterface();
+            (IPInterfaceProperties interfaceProperties, UnicastIPAddressInformation unicastAddress) = GetLocalInterface(context);
 
             if (interfaceProperties == null || unicastAddress == null)
             {
@@ -95,7 +95,7 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
         // GetCurrentIpAddress() -> nn::nifm::IpV4Address
         public ResultCode GetCurrentIpAddress(ServiceCtx context)
         {
-            (_, UnicastIPAddressInformation unicastAddress) = GetLocalInterface();
+            (_, UnicastIPAddressInformation unicastAddress) = GetLocalInterface(context);
 
             if (unicastAddress == null)
             {
@@ -113,7 +113,7 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
         // GetCurrentIpConfigInfo() -> (nn::nifm::IpAddressSetting, nn::nifm::DnsSetting)
         public ResultCode GetCurrentIpConfigInfo(ServiceCtx context)
         {
-            (IPInterfaceProperties interfaceProperties, UnicastIPAddressInformation unicastAddress) = GetLocalInterface();
+            (IPInterfaceProperties interfaceProperties, UnicastIPAddressInformation unicastAddress) = GetLocalInterface(context);
 
             if (interfaceProperties == null || unicastAddress == null)
             {
@@ -163,51 +163,23 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
             return ResultCode.Success;
         }
 
-        private (IPInterfaceProperties, UnicastIPAddressInformation) GetLocalInterface()
+        private (IPInterfaceProperties, UnicastIPAddressInformation) GetLocalInterface(ServiceCtx context)
         {
             if (!NetworkInterface.GetIsNetworkAvailable())
             {
                 return (null, null);
             }
 
-            if (_targetPropertiesCache != null && _targetAddressInfoCache != null)
-            {
-                return (_targetPropertiesCache, _targetAddressInfoCache);
-            }
-
-            IPInterfaceProperties       targetProperties  = null;
-            UnicastIPAddressInformation targetAddressInfo = null;
+            string chosenInterface = context.Device.Configuration.MultiplayerLanInterfaceId;
 
-            NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces();
-
-            foreach (NetworkInterface adapter in interfaces)
+            if (_targetPropertiesCache == null || _targetAddressInfoCache == null || _cacheChosenInterface != chosenInterface)
             {
-                // 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;
-                            }
-                        }
-                    }
-                }
-            }
+                _cacheChosenInterface = chosenInterface;
 
-            _targetPropertiesCache  = targetProperties;
-            _targetAddressInfoCache = targetAddressInfo;
+                (_targetPropertiesCache, _targetAddressInfoCache) = NetworkHelpers.GetLocalInterface(chosenInterface);
+            }
 
-            return (targetProperties, targetAddressInfo);
+            return (_targetPropertiesCache, _targetAddressInfoCache);
         }
 
         private void LocalInterfaceCacheHandler(object sender, EventArgs e)

+ 2 - 2
Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpAddressSetting.cs

@@ -18,7 +18,7 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types
             IsDhcpEnabled  = OperatingSystem.IsMacOS() || interfaceProperties.DhcpServerAddresses.Count != 0;
             Address        = new IpV4Address(unicastIPAddressInformation.Address);
             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);
         }
     }
-}
+}

+ 3 - 0
Ryujinx.Headless.SDL2/Options.cs

@@ -132,6 +132,9 @@ namespace Ryujinx.Headless.SDL2
         [Option("use-hypervisor", Required = false, Default = true, HelpText = "Uses Hypervisor over JIT if available.")]
         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
 
         [Option("disable-file-logging", Required = false, Default = false, HelpText = "Disables logging to a file on disk.")]

+ 2 - 1
Ryujinx.Headless.SDL2/Program.cs

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

+ 6 - 1
Ryujinx.Ui.Common/Configuration/ConfigurationFileFormat.cs

@@ -14,7 +14,7 @@ namespace Ryujinx.Ui.Common.Configuration
         /// <summary>
         /// The current version of the file format
         /// </summary>
-        public const int CurrentVersion = 45;
+        public const int CurrentVersion = 46;
 
         /// <summary>
         /// Version of the configuration file format
@@ -350,6 +350,11 @@ namespace Ryujinx.Ui.Common.Configuration
         /// </summary>
         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>
         /// Uses Hypervisor over JIT if available
         /// </summary>

+ 43 - 8
Ryujinx.Ui.Common/Configuration/ConfigurationState.cs

@@ -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>
         /// The default configuration instance
         /// </summary>
@@ -547,6 +563,11 @@ namespace Ryujinx.Ui.Common.Configuration
         /// </summary>
         public HidSection Hid { get; private set; }
 
+        /// <summary>
+        /// The Multiplayer section
+        /// </summary>
+        public MultiplayerSection Multiplayer { get; private set; }
+
         /// <summary>
         /// Enables or disables Discord Rich Presence
         /// </summary>
@@ -574,6 +595,7 @@ namespace Ryujinx.Ui.Common.Configuration
             System                   = new SystemSection();
             Graphics                 = new GraphicsSection();
             Hid                      = new HidSection();
+            Multiplayer              = new MultiplayerSection();
             EnableDiscordIntegration = new ReactiveObject<bool>();
             CheckUpdatesOnStart      = new ReactiveObject<bool>();
             ShowConfirmExit          = new ReactiveObject<bool>();
@@ -674,7 +696,8 @@ namespace Ryujinx.Ui.Common.Configuration
                 ControllerConfig           = new List<JsonObject>(),
                 InputConfig                = Hid.InputConfig,
                 GraphicsBackend            = Graphics.GraphicsBackend,
-                PreferredGpu               = Graphics.PreferredGpu
+                PreferredGpu               = Graphics.PreferredGpu,
+                MultiplayerLanInterfaceId  = Multiplayer.LanInterfaceId
             };
 
             return configurationFile;
@@ -727,6 +750,7 @@ namespace Ryujinx.Ui.Common.Configuration
             System.ExpandRam.Value                    = false;
             System.IgnoreMissingServices.Value        = false;
             System.UseHypervisor.Value                = true;
+            Multiplayer.LanInterfaceId.Value          = "0";
             Ui.GuiColumns.FavColumn.Value             = true;
             Ui.GuiColumns.IconColumn.Value            = true;
             Ui.GuiColumns.AppColumn.Value             = true;
@@ -1308,6 +1332,15 @@ namespace Ryujinx.Ui.Common.Configuration
                 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;
             Graphics.ResScale.Value                   = configurationFileFormat.ResScale;
             Graphics.ResScaleCustom.Value             = configurationFileFormat.ResScaleCustom;
@@ -1366,12 +1399,12 @@ namespace Ryujinx.Ui.Common.Configuration
             Ui.ColumnSort.SortColumnId.Value          = configurationFileFormat.ColumnSort.SortColumnId;
             Ui.ColumnSort.SortAscending.Value         = configurationFileFormat.ColumnSort.SortAscending;
             Ui.GameDirs.Value                         = configurationFileFormat.GameDirs;
-            Ui.ShownFileTypes.NSP.Value              = configurationFileFormat.ShownFileTypes.NSP;
-            Ui.ShownFileTypes.PFS0.Value             = configurationFileFormat.ShownFileTypes.PFS0;
-            Ui.ShownFileTypes.XCI.Value              = configurationFileFormat.ShownFileTypes.XCI;
-            Ui.ShownFileTypes.NCA.Value              = configurationFileFormat.ShownFileTypes.NCA;
-            Ui.ShownFileTypes.NRO.Value              = configurationFileFormat.ShownFileTypes.NRO;
-            Ui.ShownFileTypes.NSO.Value              = configurationFileFormat.ShownFileTypes.NSO;
+            Ui.ShownFileTypes.NSP.Value               = configurationFileFormat.ShownFileTypes.NSP;
+            Ui.ShownFileTypes.PFS0.Value              = configurationFileFormat.ShownFileTypes.PFS0;
+            Ui.ShownFileTypes.XCI.Value               = configurationFileFormat.ShownFileTypes.XCI;
+            Ui.ShownFileTypes.NCA.Value               = configurationFileFormat.ShownFileTypes.NCA;
+            Ui.ShownFileTypes.NRO.Value               = configurationFileFormat.ShownFileTypes.NRO;
+            Ui.ShownFileTypes.NSO.Value               = configurationFileFormat.ShownFileTypes.NSO;
             Ui.EnableCustomTheme.Value                = configurationFileFormat.EnableCustomTheme;
             Ui.LanguageCode.Value                     = configurationFileFormat.LanguageCode;
             Ui.CustomThemePath.Value                  = configurationFileFormat.CustomThemePath;
@@ -1393,6 +1426,8 @@ namespace Ryujinx.Ui.Common.Configuration
                 Hid.InputConfig.Value = new List<InputConfig>();
             }
 
+            Multiplayer.LanInterfaceId.Value = configurationFileFormat.MultiplayerLanInterfaceId;
+
             if (configurationFileUpdated)
             {
                 ToFileFormat().SaveConfig(configurationFilePath);
@@ -1418,4 +1453,4 @@ namespace Ryujinx.Ui.Common.Configuration
             Instance = new ConfigurationState();
         }
     }
-}
+}

+ 2 - 1
Ryujinx/Ui/MainWindow.cs

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

+ 18 - 0
Ryujinx/Ui/Windows/SettingsWindow.cs

@@ -17,6 +17,7 @@ using System;
 using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
+using System.Net.NetworkInformation;
 using System.Reflection;
 using System.Threading.Tasks;
 using GUI = Gtk.Builder.ObjectAttribute;
@@ -84,6 +85,7 @@ namespace Ryujinx.Ui.Windows
         [GUI] Adjustment      _systemTimeDaySpinAdjustment;
         [GUI] Adjustment      _systemTimeHourSpinAdjustment;
         [GUI] Adjustment      _systemTimeMinuteSpinAdjustment;
+        [GUI] ComboBoxText    _multiLanSelect;
         [GUI] CheckButton     _custThemeToggle;
         [GUI] Entry           _custThemePath;
         [GUI] ToggleButton    _browseThemePath;
@@ -348,6 +350,8 @@ namespace Ryujinx.Ui.Windows
             UpdatePreferredGpuComboBox();
 
             _graphicsBackend.Changed += (sender, e) => UpdatePreferredGpuComboBox();
+            PopulateNetworkInterfaces();
+            _multiLanSelect.SetActiveId(ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value);
 
             _custThemePath.Buffer.Text           = ConfigurationState.Instance.Ui.CustomThemePath;
             _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()
         {
             //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.ScalingFilter.Value              = Enum.Parse<ScalingFilter>(_scalingFilter.ActiveId);
             ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value         = (int)_scalingFilterLevel.Value;
+            ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value          = _multiLanSelect.ActiveId;
 
             _previousVolumeLevel = ConfigurationState.Instance.System.AudioVolume.Value;
 

+ 137 - 8
Ryujinx/Ui/Windows/SettingsWindow.glade

@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.38.2 -->
+<!-- Generated with glade 3.40.0 -->
 <interface>
   <requires lib="gtk+" version="3.20"/>
   <object class="GtkAdjustment" id="_fsLogSpinAdjustment">
@@ -7,6 +7,12 @@
     <property name="step-increment">1</property>
     <property name="page-increment">10</property>
   </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">
     <property name="lower">1</property>
     <property name="upper">31</property>
@@ -40,13 +46,6 @@
     <property name="inline-completion">True</property>
     <property name="inline-selection">True</property>
   </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">
     <property name="can-focus">False</property>
     <property name="title" translatable="yes">Ryujinx - Settings</property>
@@ -2862,6 +2861,136 @@
                         <property name="tab-fill">False</property>
                       </packing>
                     </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>
                 </child>
               </object>