diff --git a/src/Ryujinx.HLE/HLEConfiguration.cs b/src/Ryujinx.HLE/HLEConfiguration.cs index ece36377c..70fcf278d 100644 --- a/src/Ryujinx.HLE/HLEConfiguration.cs +++ b/src/Ryujinx.HLE/HLEConfiguration.cs @@ -174,6 +174,11 @@ public class HLEConfiguration /// public string MultiplayerLdnPassphrase { internal get; set; } + /// + /// LDN Server + /// + public string MultiplayerLdnServer { internal get; set; } + /// /// An action called when HLE force a refresh of output after docked mode changed. /// @@ -206,7 +211,8 @@ public HLEConfiguration(VirtualFileSystem virtualFileSystem, string multiplayerLanInterfaceId, MultiplayerMode multiplayerMode, bool multiplayerDisableP2p, - string multiplayerLdnPassphrase) + string multiplayerLdnPassphrase, + string multiplayerLdnServer) { VirtualFileSystem = virtualFileSystem; LibHacHorizonManager = libHacHorizonManager; @@ -236,6 +242,7 @@ public HLEConfiguration(VirtualFileSystem virtualFileSystem, MultiplayerMode = multiplayerMode; MultiplayerDisableP2p = multiplayerDisableP2p; MultiplayerLdnPassphrase = multiplayerLdnPassphrase; + MultiplayerLdnServer = multiplayerLdnServer; } } } diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs index e769060a5..9f65aed4b 100644 --- a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs @@ -23,7 +23,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator { class IUserLocalCommunicationService : IpcService, IDisposable { - public static string LanPlayHost = "ryuldn.vudjun.com"; + public static string DefaultLanPlayHost = "ryuldn.vudjun.com"; public static short LanPlayPort = 30456; public INetworkClient NetworkClient { get; private set; } @@ -1092,15 +1092,21 @@ public ResultCode InitializeImpl(ServiceCtx context, ulong pid, int nifmRequestI case MultiplayerMode.LdnRyu: try { - if (!IPAddress.TryParse(LanPlayHost, out IPAddress ipAddress)) + string ldnServer = context.Device.Configuration.MultiplayerLdnServer; + if (string.IsNullOrEmpty(ldnServer)) { - ipAddress = Dns.GetHostEntry(LanPlayHost).AddressList[0]; + ldnServer = DefaultLanPlayHost; + } + if (!IPAddress.TryParse(ldnServer, out IPAddress ipAddress)) + { + ipAddress = Dns.GetHostEntry(ldnServer).AddressList[0]; } NetworkClient = new LdnMasterProxyClient(ipAddress.ToString(), LanPlayPort, context.Device.Configuration); } - catch (Exception) + catch (Exception ex) { Logger.Error?.Print(LogClass.ServiceLdn, "Could not locate LdnRyu server. Defaulting to stubbed wireless."); + Logger.Error?.Print(LogClass.ServiceLdn, ex.Message); NetworkClient = new LdnDisabledClient(); } break; diff --git a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs index b4b3fa4d2..174db51ad 100644 --- a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs +++ b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs @@ -44,6 +44,7 @@ namespace Ryujinx.UI.App.Common { public class ApplicationLibrary { + public static string DefaultLanPlayWebHost = "ryuldnweb.vudjun.com"; public Language DesiredLanguage { get; set; } public event EventHandler ApplicationCountUpdated; public event EventHandler LdnGameDataReceived; @@ -788,9 +789,14 @@ public async Task RefreshLdn() { try { + string ldnWebHost = ConfigurationState.Instance.Multiplayer.LdnServer; + if (string.IsNullOrEmpty(ldnWebHost)) + { + ldnWebHost = DefaultLanPlayWebHost; + } IEnumerable ldnGameDataArray = Array.Empty(); using HttpClient httpClient = new HttpClient(); - string ldnGameDataArrayString = await httpClient.GetStringAsync("https://ryuldnweb.vudjun.com/api/public_games"); + string ldnGameDataArrayString = await httpClient.GetStringAsync($"https://{ldnWebHost}/api/public_games"); ldnGameDataArray = JsonHelper.Deserialize(ldnGameDataArrayString, _ldnDataSerializerContext.IEnumerableLdnGameData); var evt = new LdnGameDataReceivedEventArgs { @@ -798,9 +804,9 @@ public async Task RefreshLdn() }; LdnGameDataReceived?.Invoke(null, evt); } - catch + catch (Exception ex) { - Logger.Warning?.Print(LogClass.Application, "Failed to fetch the public games JSON from the API. Player and game count in the game list will be unavailable."); + Logger.Warning?.Print(LogClass.Application, $"Failed to fetch the public games JSON from the API. Player and game count in the game list will be unavailable.\n{ex.Message}"); LdnGameDataReceived?.Invoke(null, new LdnGameDataReceivedEventArgs() { LdnData = Array.Empty() diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs index c2dd6a710..80ba1b186 100644 --- a/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs @@ -402,6 +402,11 @@ public class ConfigurationFileFormat /// public string MultiplayerLdnPassphrase { get; set; } + /// + /// Custom LDN Server + /// + public string LdnServer { get; set; } + /// /// Uses Hypervisor over JIT if available /// diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs index 6b3c8db3b..c493bd34f 100644 --- a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs @@ -584,6 +584,11 @@ public class MultiplayerSection /// public ReactiveObject LdnPassphrase { get; private set; } + /// + /// Custom LDN server + /// + public ReactiveObject LdnServer { get; private set; } + public MultiplayerSection() { LanInterfaceId = new ReactiveObject(); @@ -592,6 +597,7 @@ public MultiplayerSection() DisableP2p = new ReactiveObject(); DisableP2p.Event += static (_, e) => LogValueChange(e, nameof(DisableP2p)); LdnPassphrase = new ReactiveObject(); + LdnServer = new ReactiveObject(); } } @@ -801,6 +807,7 @@ public ConfigurationFileFormat ToFileFormat() MultiplayerMode = Multiplayer.Mode, MultiplayerDisableP2p = Multiplayer.DisableP2p, MultiplayerLdnPassphrase = Multiplayer.LdnPassphrase, + LdnServer = Multiplayer.LdnServer, }; return configurationFile; @@ -862,6 +869,7 @@ public void LoadDefault() Multiplayer.Mode.Value = MultiplayerMode.Disabled; Multiplayer.DisableP2p.Value = false; Multiplayer.LdnPassphrase.Value = ""; + Multiplayer.LdnServer.Value = ""; UI.GuiColumns.FavColumn.Value = true; UI.GuiColumns.IconColumn.Value = true; UI.GuiColumns.AppColumn.Value = true; @@ -1656,6 +1664,7 @@ public void Load(ConfigurationFileFormat configurationFileFormat, string configu Multiplayer.Mode.Value = configurationFileFormat.MultiplayerMode; Multiplayer.DisableP2p.Value = configurationFileFormat.MultiplayerDisableP2p; Multiplayer.LdnPassphrase.Value = configurationFileFormat.MultiplayerLdnPassphrase; + Multiplayer.LdnServer.Value = configurationFileFormat.LdnServer; if (configurationFileUpdated) { diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs index a6abe84d8..7246be4b9 100644 --- a/src/Ryujinx/AppHost.cs +++ b/src/Ryujinx/AppHost.cs @@ -208,6 +208,7 @@ public AppHost( ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState; ConfigurationState.Instance.Multiplayer.Mode.Event += UpdateMultiplayerModeState; ConfigurationState.Instance.Multiplayer.LdnPassphrase.Event += UpdateLdnPassphraseState; + ConfigurationState.Instance.Multiplayer.LdnServer.Event += UpdateLdnServerState; ConfigurationState.Instance.Multiplayer.DisableP2p.Event += UpdateDisableP2pState; _gpuCancellationTokenSource = new CancellationTokenSource(); @@ -498,6 +499,11 @@ private void UpdateLdnPassphraseState(object sender, ReactiveEventArgs e Device.Configuration.MultiplayerLdnPassphrase = e.NewValue; } + private void UpdateLdnServerState(object sender, ReactiveEventArgs e) + { + Device.Configuration.MultiplayerLdnServer = e.NewValue; + } + private void UpdateDisableP2pState(object sender, ReactiveEventArgs e) { Device.Configuration.MultiplayerDisableP2p = e.NewValue; @@ -878,7 +884,8 @@ private void InitializeSwitchInstance() ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value, ConfigurationState.Instance.Multiplayer.Mode, ConfigurationState.Instance.Multiplayer.DisableP2p, - ConfigurationState.Instance.Multiplayer.LdnPassphrase)); + ConfigurationState.Instance.Multiplayer.LdnPassphrase, + ConfigurationState.Instance.Multiplayer.LdnServer)); } private static IHardwareDeviceDriver InitializeAudio() diff --git a/src/Ryujinx/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json index e63208d59..de30fd63f 100644 --- a/src/Ryujinx/Assets/Locales/en_US.json +++ b/src/Ryujinx/Assets/Locales/en_US.json @@ -819,5 +819,9 @@ "GenLdnPassTooltip": "Generates a new passphrase, which can be shared with other players.", "ClearLdnPass": "Clear", "ClearLdnPassTooltip": "Clears the current passphrase, returning to the public network.", - "InvalidLdnPassphrase": "Invalid Passphrase! Must be in the format \"Ryujinx-<8 hex chars>\"" + "InvalidLdnPassphrase": "Invalid Passphrase! Must be in the format \"Ryujinx-<8 hex chars>\"", + "LdnServer": "Custom LDN Server:", + "LdnServerTooltip": "The LDN server to use for LDN connections. Leave blank to use the default server.", + "LdnServerInputTooltip": "Enter the URL of the LDN server to use.", + "LdnServerInputDefault": "(default)" } diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index 6896e56af..2da252d00 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -58,6 +58,7 @@ public partial class SettingsViewModel : BaseModel private int _networkInterfaceIndex; private int _multiplayerModeIndex; private string _ldnPassphrase; + private string _LdnServer; public int ResolutionScale { @@ -297,6 +298,16 @@ public int MultiplayerModeIndex public bool IsInvalidLdnPassphraseVisible { get; set; } + public string LdnServer + { + get => _LdnServer; + set + { + _LdnServer = value; + OnPropertyChanged(); + } + } + public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this() { _virtualFileSystem = virtualFileSystem; @@ -525,6 +536,7 @@ public void LoadCurrentConfiguration() MultiplayerModeIndex = (int)config.Multiplayer.Mode.Value; DisableP2P = config.Multiplayer.DisableP2p.Value; LdnPassphrase = config.Multiplayer.LdnPassphrase.Value; + LdnServer = config.Multiplayer.LdnServer.Value; } public void SaveSettings() @@ -643,6 +655,7 @@ public void SaveSettings() config.Multiplayer.Mode.Value = (MultiplayerMode)MultiplayerModeIndex; config.Multiplayer.DisableP2p.Value = DisableP2P; config.Multiplayer.LdnPassphrase.Value = LdnPassphrase; + config.Multiplayer.LdnServer.Value = LdnServer; config.ToFileFormat().SaveConfig(Program.ConfigurationPath); diff --git a/src/Ryujinx/UI/Views/Settings/SettingsNetworkView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsNetworkView.axaml index d23bbbccc..79c1e90f0 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsNetworkView.axaml +++ b/src/Ryujinx/UI/Views/Settings/SettingsNetworkView.axaml @@ -1,4 +1,4 @@ - + + + + diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs index 9416cf477..298ef2d55 100644 --- a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs @@ -501,6 +501,11 @@ protected override void OnOpened(EventArgs e) { _ = Task.Run(ViewModel.ApplicationLibrary.RefreshLdn); }; + + ConfigurationState.Instance.Multiplayer.LdnServer.Event += (sender, evt) => + { + _ = Task.Run(ViewModel.ApplicationLibrary.RefreshLdn); + }; _ = Task.Run(ViewModel.ApplicationLibrary.RefreshLdn); ViewModel.RefreshFirmwareStatus();