Skip to content

Commit

Permalink
TESTERS WANTED: RyuLDN implementation (#65)
Browse files Browse the repository at this point in the history
These changes allow players to matchmake for local wireless using a LDN
server. The network implementation originates from Berry's public TCP
RyuLDN fork. Logo and unrelated changes have been removed.

Additionally displays LDN game status in the game selection window when
RyuLDN is enabled.

Functionality is only enabled while network mode is set to "RyuLDN" in
the settings.
  • Loading branch information
Vudjun authored Nov 11, 2024
1 parent abfcfca commit 6d8738c
Show file tree
Hide file tree
Showing 93 changed files with 4,093 additions and 182 deletions.
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
<PackageVersion Include="OpenTK.Graphics" Version="4.8.2" />
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.8.2" />
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.8.2" />
<PackageVersion Include="Open.NAT.Core" Version="2.1.0.5" />
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.3-build14" />
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ namespace Ryujinx.Common.Configuration.Multiplayer
public enum MultiplayerMode
{
Disabled,
LdnRyu,
LdnMitm,
}
}
18 changes: 9 additions & 9 deletions src/Ryujinx.Common/Memory/StructArrayHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -803,25 +803,25 @@ public struct Array128<T> : IArray<T> where T : unmanaged
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
}

public struct Array256<T> : IArray<T> where T : unmanaged
public struct Array140<T> : IArray<T> where T : unmanaged
{
T _e0;
Array128<T> _other;
Array127<T> _other2;
public readonly int Length => 256;
Array64<T> _other;
Array64<T> _other2;
Array11<T> _other3;
public readonly int Length => 140;
public ref T this[int index] => ref AsSpan()[index];

[Pure]
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
}

public struct Array140<T> : IArray<T> where T : unmanaged
public struct Array256<T> : IArray<T> where T : unmanaged
{
T _e0;
Array64<T> _other;
Array64<T> _other2;
Array11<T> _other3;
public readonly int Length => 140;
Array128<T> _other;
Array127<T> _other2;
public readonly int Length => 256;
public ref T this[int index] => ref AsSpan()[index];

[Pure]
Expand Down
6 changes: 6 additions & 0 deletions src/Ryujinx.Common/Utilities/NetworkHelpers.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Buffers.Binary;
using System.Net;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;

namespace Ryujinx.Common.Utilities
{
Expand Down Expand Up @@ -65,6 +66,11 @@ public static (IPInterfaceProperties, UnicastIPAddressInformation) GetLocalInter
return (targetProperties, targetAddressInfo);
}

public static bool SupportsDynamicDns()
{
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
}

public static uint ConvertIpv4Address(IPAddress ipAddress)
{
return BinaryPrimitives.ReadUInt32BigEndian(ipAddress.GetAddressBytes());
Expand Down
2 changes: 1 addition & 1 deletion src/Ryujinx.Graphics.GAL/IRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public interface IRenderer : IDisposable
IPipeline Pipeline { get; }

IWindow Window { get; }

uint ProgramCount { get; }

void BackgroundContextAction(Action action, bool alwaysBackground = false);
Expand Down
2 changes: 1 addition & 1 deletion src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public IImageArray CreateImageArray(int size, bool isBuffer)
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
{
ProgramCount++;

return new Program(shaders, info.FragmentOutputMap);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ public IImageArray CreateImageArray(int size, bool isBuffer)
public IProgram CreateProgram(ShaderSource[] sources, ShaderInfo info)
{
ProgramCount++;

bool isCompute = sources.Length == 1 && sources[0].Stage == ShaderStage.Compute;

if (info.State.HasValue || isCompute)
Expand Down
23 changes: 22 additions & 1 deletion src/Ryujinx.HLE/HLEConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,21 @@ public class HLEConfiguration
/// </summary>
public MultiplayerMode MultiplayerMode { internal get; set; }

/// <summary>
/// Disable P2P mode
/// </summary>
public bool MultiplayerDisableP2p { internal get; set; }

/// <summary>
/// Multiplayer Passphrase
/// </summary>
public string MultiplayerLdnPassphrase { internal get; set; }

/// <summary>
/// LDN Server
/// </summary>
public string MultiplayerLdnServer { internal get; set; }

/// <summary>
/// An action called when HLE force a refresh of output after docked mode changed.
/// </summary>
Expand Down Expand Up @@ -194,7 +209,10 @@ public HLEConfiguration(VirtualFileSystem virtualFileSystem,
float audioVolume,
bool useHypervisor,
string multiplayerLanInterfaceId,
MultiplayerMode multiplayerMode)
MultiplayerMode multiplayerMode,
bool multiplayerDisableP2p,
string multiplayerLdnPassphrase,
string multiplayerLdnServer)
{
VirtualFileSystem = virtualFileSystem;
LibHacHorizonManager = libHacHorizonManager;
Expand Down Expand Up @@ -222,6 +240,9 @@ public HLEConfiguration(VirtualFileSystem virtualFileSystem,
UseHypervisor = useHypervisor;
MultiplayerLanInterfaceId = multiplayerLanInterfaceId;
MultiplayerMode = multiplayerMode;
MultiplayerDisableP2p = multiplayerDisableP2p;
MultiplayerLdnPassphrase = multiplayerLdnPassphrase;
MultiplayerLdnServer = multiplayerLdnServer;
}
}
}
2 changes: 1 addition & 1 deletion src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Ryujinx.HLE.HOS.Services.Ldn.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x20)]
[StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = 8)]
struct NetworkConfig
{
public IntentId IntentId;
Expand Down
2 changes: 1 addition & 1 deletion src/Ryujinx.HLE/HOS/Services/Ldn/Types/ScanFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Ryujinx.HLE.HOS.Services.Ldn.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x60)]
[StructLayout(LayoutKind.Sequential, Size = 0x60, Pack = 8)]
struct ScanFilter
{
public NetworkId NetworkId;
Expand Down
2 changes: 1 addition & 1 deletion src/Ryujinx.HLE/HOS/Services/Ldn/Types/SecurityConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Ryujinx.HLE.HOS.Services.Ldn.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x44)]
[StructLayout(LayoutKind.Sequential, Size = 0x44, Pack = 2)]
struct SecurityConfig
{
public SecurityMode SecurityMode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Ryujinx.HLE.HOS.Services.Ldn.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x20)]
[StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = 1)]
struct SecurityParameter
{
public Array16<byte> Data;
Expand Down
2 changes: 1 addition & 1 deletion src/Ryujinx.HLE/HOS/Services/Ldn/Types/UserConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Ryujinx.HLE.HOS.Services.Ldn.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x30)]
[StructLayout(LayoutKind.Sequential, Size = 0x30, Pack = 1)]
struct UserConfig
{
public Array33<byte> UserName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ class AccessPoint : IDisposable
public Array8<NodeLatestUpdate> LatestUpdates = new();
public bool Connected { get; private set; }

public ProxyConfig Config => _parent.NetworkClient.Config;

public AccessPoint(IUserLocalCommunicationService parent)
{
_parent = parent;
Expand All @@ -24,9 +26,12 @@ public AccessPoint(IUserLocalCommunicationService parent)

public void Dispose()
{
_parent.NetworkClient.DisconnectNetwork();
if (_parent?.NetworkClient != null)
{
_parent.NetworkClient.DisconnectNetwork();

_parent.NetworkClient.NetworkChange -= NetworkChanged;
_parent.NetworkClient.NetworkChange -= NetworkChanged;
}
}

private void NetworkChanged(object sender, NetworkChangeEventArgs e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
{
interface INetworkClient : IDisposable
{
ProxyConfig Config { get; }
bool NeedsRealId { get; }

event EventHandler<NetworkChangeEventArgs> NetworkChange;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Ldn.Types;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
using Ryujinx.Horizon.Common;
using Ryujinx.Memory;
using System;
Expand All @@ -21,6 +23,9 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
{
class IUserLocalCommunicationService : IpcService, IDisposable
{
public static string DefaultLanPlayHost = "ryuldn.vudjun.com";
public static short LanPlayPort = 30456;

public INetworkClient NetworkClient { get; private set; }

private const int NifmRequestID = 90;
Expand Down Expand Up @@ -175,19 +180,37 @@ public ResultCode GetIpv4Address(ServiceCtx context)

if (_state == NetworkState.AccessPointCreated || _state == NetworkState.StationConnected)
{
(_, UnicastIPAddressInformation unicastAddress) = NetworkHelpers.GetLocalInterface(context.Device.Configuration.MultiplayerLanInterfaceId);
ProxyConfig config = _state switch
{
NetworkState.AccessPointCreated => _accessPoint.Config,
NetworkState.StationConnected => _station.Config,

if (unicastAddress == null)
_ => default
};

if (config.ProxyIp == 0)
{
context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(DefaultIPAddress));
context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(DefaultSubnetMask));
(_, UnicastIPAddressInformation unicastAddress) = NetworkHelpers.GetLocalInterface(context.Device.Configuration.MultiplayerLanInterfaceId);

if (unicastAddress == null)
{
context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(DefaultIPAddress));
context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(DefaultSubnetMask));
}
else
{
Logger.Info?.Print(LogClass.ServiceLdn, $"Console's LDN IP is \"{unicastAddress.Address}\".");

context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(unicastAddress.Address));
context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(unicastAddress.IPv4Mask));
}
}
else
{
Logger.Info?.Print(LogClass.ServiceLdn, $"Console's LDN IP is \"{unicastAddress.Address}\".");
Logger.Info?.Print(LogClass.ServiceLdn, $"LDN obtained proxy IP.");

context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(unicastAddress.Address));
context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(unicastAddress.IPv4Mask));
context.ResponseData.Write(config.ProxyIp);
context.ResponseData.Write(config.ProxySubnetMask);
}
}
else
Expand Down Expand Up @@ -1066,6 +1089,27 @@ public ResultCode InitializeImpl(ServiceCtx context, ulong pid, int nifmRequestI

switch (mode)
{
case MultiplayerMode.LdnRyu:
try
{
string ldnServer = context.Device.Configuration.MultiplayerLdnServer;
if (string.IsNullOrEmpty(ldnServer))
{
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 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;
case MultiplayerMode.LdnMitm:
NetworkClient = new LdnMitmClient(context.Device.Configuration);
break;
Expand Down Expand Up @@ -1103,7 +1147,7 @@ public void Dispose()
_accessPoint?.Dispose();
_accessPoint = null;

NetworkClient?.Dispose();
NetworkClient?.DisconnectAndStop();
NetworkClient = null;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Ldn.Types;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
using System;
Expand All @@ -6,33 +7,38 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
{
class LdnDisabledClient : INetworkClient
{
public ProxyConfig Config { get; }
public bool NeedsRealId => true;

public event EventHandler<NetworkChangeEventArgs> NetworkChange;

public NetworkError Connect(ConnectRequest request)
{
Logger.Warning?.PrintMsg(LogClass.ServiceLdn, "Attempted to connect to a network, but Multiplayer is disabled!");
NetworkChange?.Invoke(this, new NetworkChangeEventArgs(new NetworkInfo(), false));

return NetworkError.None;
}

public NetworkError ConnectPrivate(ConnectPrivateRequest request)
{
Logger.Warning?.PrintMsg(LogClass.ServiceLdn, "Attempted to connect to a network, but Multiplayer is disabled!");
NetworkChange?.Invoke(this, new NetworkChangeEventArgs(new NetworkInfo(), false));

return NetworkError.None;
}

public bool CreateNetwork(CreateAccessPointRequest request, byte[] advertiseData)
{
Logger.Warning?.PrintMsg(LogClass.ServiceLdn, "Attempted to create a network, but Multiplayer is disabled!");
NetworkChange?.Invoke(this, new NetworkChangeEventArgs(new NetworkInfo(), false));

return true;
}

public bool CreateNetworkPrivate(CreateAccessPointPrivateRequest request, byte[] advertiseData)
{
Logger.Warning?.PrintMsg(LogClass.ServiceLdn, "Attempted to create a network, but Multiplayer is disabled!");
NetworkChange?.Invoke(this, new NetworkChangeEventArgs(new NetworkInfo(), false));

return true;
Expand All @@ -49,6 +55,7 @@ public ResultCode Reject(DisconnectReason disconnectReason, uint nodeId)

public NetworkInfo[] Scan(ushort channel, ScanFilter scanFilter)
{
Logger.Warning?.PrintMsg(LogClass.ServiceLdn, "Attempted to scan for networks, but Multiplayer is disabled!");
return Array.Empty<NetworkInfo>();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm
/// </summary>
internal class LdnMitmClient : INetworkClient
{
public ProxyConfig Config { get; }
public bool NeedsRealId => false;

public event EventHandler<NetworkChangeEventArgs> NetworkChange;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu
{
interface IProxyClient
{
bool SendAsync(byte[] buffer);
}
}
Loading

0 comments on commit 6d8738c

Please sign in to comment.