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();