Skip to content

Commit

Permalink
RyuLDN server implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Vudjun committed Oct 13, 2024
1 parent 59097c5 commit 2de27b1
Show file tree
Hide file tree
Showing 8 changed files with 815 additions and 0 deletions.
6 changes: 6 additions & 0 deletions Ryujinx.sln
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Gene
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.RyuLDNServer", "src\Ryujinx.RyuLDNServer\Ryujinx.RyuLDNServer.csproj", "{626EBA4A-7A27-4A44-8625-0413545BCC47}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -255,6 +257,10 @@ Global
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.Build.0 = Release|Any CPU
{626EBA4A-7A27-4A44-8625-0413545BCC47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{626EBA4A-7A27-4A44-8625-0413545BCC47}.Debug|Any CPU.Build.0 = Debug|Any CPU
{626EBA4A-7A27-4A44-8625-0413545BCC47}.Release|Any CPU.ActiveCfg = Release|Any CPU
{626EBA4A-7A27-4A44-8625-0413545BCC47}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
1 change: 1 addition & 0 deletions src/Ryujinx.HLE/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Ryujinx.Tests")]
[assembly: InternalsVisibleTo("Ryujinx.RyuLDNServer")]
22 changes: 22 additions & 0 deletions src/Ryujinx.RyuLDNServer/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Ryujinx.RyuLDNServer
{
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Starting RyuLDN server");
RyuLDNServer server = new RyuLDNServer();
var started = server.Start();
if (started)
{
Console.WriteLine("RyuLDN server started");
Console.WriteLine("Press any key to stop the server");
Console.ReadKey();
}
else
{
Console.WriteLine("Error");
}
}
}
}
22 changes: 22 additions & 0 deletions src/Ryujinx.RyuLDNServer/Room.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS.Services.Ldn.Types;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Ryujinx.RyuLDNServer
{
internal class Room
{
public NetworkInfo NetworkInfo;
public List<RyuLdnSession> Sessions = new List<RyuLdnSession>();
public RyuLdnSession hostSession;
public uint nextFakeIp;
public uint fakeNetworkSubnetMask;
public string gameVersion;
public RyuNetworkConfig networkConfig;
}
}
274 changes: 274 additions & 0 deletions src/Ryujinx.RyuLDNServer/RyuLdnManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS.Services.Ldn;
using Ryujinx.HLE.HOS.Services.Ldn.Types;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
using Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using static Ryujinx.RyuLDNServer.RyuLdnSession;

namespace Ryujinx.RyuLDNServer
{
internal class RyuLdnManager
{
// TODO once connected, ping session every 10 seconds to ensure connected
private static byte[] _emptyId = new byte[16];

public List<Room> RoomList = new List<Room>();
private Random _random { get; set; } = new Random();

internal void InitializeClient(RyuLdnSession session, InitializeMessage message)
{
//if (message.Id.AsSpan().SequenceEqual(_emptyId))
{
Random random = new Random();
random.NextBytes(message.Id.AsSpan());
random.NextBytes(message.MacAddress.AsSpan());
session.InitializeData = message;
session._state = RyuLdnSession.SessionState.Initialized;
session.SendAsync(session.Protocol.Encode(PacketId.Initialize, message));
}
//else
//{
// Logger.Info?.PrintMsg(LogClass.ServiceLdn, "Client already has an ID");
// session._state = RyuLdnSession.SessionState.Initialized;
// session.SendAsync(session.Protocol.Encode(PacketId.Initialize, message));
// // TODO validate client id from cache
//}
}

internal void SendGameList(RyuLdnSession session, ScanFilter filter)
{
// TODO filter on StationAcceptPolicy too? If it's set to deny all the session is probably not joinable so shouldn't be sent.
var filterFlag = filter.Flag;
foreach (var room in RoomList)
{
if (!session.PassPhrase.AsSpan().SequenceEqual(room.hostSession.PassPhrase.AsSpan()))
{
continue;
}

if (filter.Flag.HasFlag(ScanFilterFlag.LocalCommunicationId))
{
if (filter.NetworkId.IntentId.LocalCommunicationId != room.NetworkInfo.NetworkId.IntentId.LocalCommunicationId)
{
continue;
}
}

if (filter.Flag.HasFlag(ScanFilterFlag.SessionId))
{
if (!filter.NetworkId.SessionId.AsSpan().SequenceEqual(room.NetworkInfo.NetworkId.SessionId.AsSpan())) {
continue;
}
}

if (filter.Flag.HasFlag(ScanFilterFlag.NetworkType))
{
if (filter.NetworkType != (NetworkType)room.NetworkInfo.Common.NetworkType)
{
continue;
}
}

if (filter.Flag.HasFlag(ScanFilterFlag.Ssid))
{
Span<byte> gameSsid = room.NetworkInfo.Common.Ssid.Name.AsSpan()[room.NetworkInfo.Common.Ssid.Length..];
Span<byte> scanSsid = filter.Ssid.Name.AsSpan()[filter.Ssid.Length..];
if (!gameSsid.SequenceEqual(scanSsid))
{
continue;
}
}

if (filter.Flag.HasFlag(ScanFilterFlag.SceneId))
{
if (filter.NetworkId.IntentId.SceneId != room.NetworkInfo.NetworkId.IntentId.SceneId)
{
continue;
}
}

if (room.hostSession.UserName[0] != 0)
{
session.SendAsync(session.Protocol.Encode(PacketId.ScanReply, room.NetworkInfo));
}
else
{
Logger.Warning?.PrintMsg(LogClass.ServiceLdn, "LdnManager Scan: Got empty Username. There might be a timing issue somewhere...");
}
}
Task.Delay(250).Wait(); // Reduces scan frequency a little by delaying the end packet
session.SendAsync(session.Protocol.Encode(PacketId.ScanReplyEnd));
}

internal Room CreateRoom(RyuLdnSession session, NetworkInfo networkInfo, RyuNetworkConfig ryuNetworkConfig)
{
var gameVersion = Encoding.ASCII.GetString(ryuNetworkConfig.GameVersion.AsSpan()).TrimEnd('\0');
_random.NextBytes(networkInfo.NetworkId.SessionId.AsSpan());

networkInfo.Common.Ssid.Length = 32;
Encoding.UTF8.GetBytes("12345678123456781234567812345678").CopyTo(networkInfo.Common.Ssid.Name.AsSpan());
networkInfo.Ldn.SecurityMode = 1;
var room = new Room()
{
NetworkInfo = networkInfo,
Sessions = new List<RyuLdnSession>(),
nextFakeIp = NetworkHelpers.ConvertIpv4Address("10.114.0.1"),
fakeNetworkSubnetMask = NetworkHelpers.ConvertIpv4Address("255.255.0.0"),
gameVersion = gameVersion,
networkConfig = ryuNetworkConfig,
hostSession = session
};
bool isP2P = ryuNetworkConfig.ExternalProxyPort != 0;
if (isP2P)
{
bool isAccessible = true;
// probe the port to verify externally reachable
try
{
using (var client = new TcpClient())
{
client.ReceiveTimeout = 2000;
client.SendTimeout = 2000;
client.Connect(((IPEndPoint)session.Socket.RemoteEndPoint!).Address, ryuNetworkConfig.ExternalProxyPort);
}
}
catch (Exception)
{
isAccessible = false;
}
if (!isAccessible)
{
session.SendAsync(session.Protocol.Encode(PacketId.NetworkError, new NetworkErrorMessage()
{
Error = NetworkError.PortUnreachable
}));
}
}
RoomList.Add(room);
return room;
}
internal void AddSessionToRoom(Room room, RyuLdnSession session, UserConfig userConfig)
{
session._state = SessionState.Connected;
room.Sessions.Add(session);
session.Room = room;
session.fakeIp = room.nextFakeIp++;
// If P2P is enabled
if (room.networkConfig.ExternalProxyPort != 0)
{
var token = new Array16<byte>();
_random.NextBytes(token.AsSpan());
bool isPrivate = session.ip.AsSpan().SequenceEqual(room.hostSession.ip.AsSpan());
ExternalProxyConfig config;
// If same network, send private IP, since same-IP connections through a uPNP opened port on the router often fails depending on router configuration
if (isPrivate)
{
room.hostSession.SendAsync(room.hostSession.Protocol.Encode(PacketId.ExternalProxyToken, new ExternalProxyToken()
{
AddressFamily = AddressFamily.InterNetwork,
Token = token,
VirtualIp = session.fakeIp
}));
session.SendAsync(session.Protocol.Encode(PacketId.ExternalProxy, new ExternalProxyConfig()
{
AddressFamily = room.networkConfig.AddressFamily,
ProxyIp = room.networkConfig.PrivateIp,
ProxyPort = room.networkConfig.InternalProxyPort,
Token = token
}));
}
else
{
room.hostSession.SendAsync(room.hostSession.Protocol.Encode(PacketId.ExternalProxyToken, new ExternalProxyToken()
{
AddressFamily = session.addressFamily,
PhysicalIp = session.ip,
Token = token,
VirtualIp = session.fakeIp
}));
session.SendAsync(session.Protocol.Encode(PacketId.ExternalProxy, new ExternalProxyConfig()
{
AddressFamily = room.hostSession.addressFamily,
ProxyIp = room.hostSession.ip,
ProxyPort = room.networkConfig.ExternalProxyPort,
Token = token
}));
}
}
else
{
session.SendAsync(session.Protocol.Encode(PacketId.ProxyConfig, new ProxyConfig()
{
ProxyIp = session.fakeIp,
ProxySubnetMask = room.fakeNetworkSubnetMask
}));
}
SyncNetwork(room, session);
}

internal void SyncNetwork(Room room, RyuLdnSession? connectedSession = null)
{
for (var i = 0; i < 8; i++)
{
var session = room.Sessions.ElementAtOrDefault(i);
if (session == null)
{
room.NetworkInfo.Ldn.Nodes[i] = new NodeInfo();
}
else
{
room.NetworkInfo.Ldn.Nodes[i] = new NodeInfo()
{
MacAddress = session.InitializeData.MacAddress,
NodeId = (byte)i,
IsConnected = 1,
UserName = session.UserName,
LocalCommunicationVersion = (ushort)session.LocalCommunicationVersion,
Ipv4Address = session.fakeIp
};
}
}
room.NetworkInfo.Ldn.NodeCount = (byte)room.Sessions.Count;
foreach (var session in room.Sessions)
{
if (session == connectedSession)
{
session.SendAsync(session.Protocol.Encode(PacketId.Connected, room.NetworkInfo));
}
else
{
session.SendAsync(session.Protocol.Encode(PacketId.SyncNetwork, room.NetworkInfo));
}
}
}

internal void CloseRoom(Room room)
{
foreach (var session in room.Sessions)
{
session.SendAsync(session.Protocol.Encode(PacketId.Disconnect, new DisconnectMessage()
{
DisconnectIP = 0
}));
session.Disconnect();
}
RoomList.Remove(room);
}

internal void RemoveSessionFromRoom(Room room, RyuLdnSession ryuLdnSession)
{
room.Sessions.Remove(ryuLdnSession);
SyncNetwork(room);
}
}
}
45 changes: 45 additions & 0 deletions src/Ryujinx.RyuLDNServer/RyuLdnServer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using LibHac.Sdmmc;
using NetCoreServer;
using Open.Nat;
using Ryujinx.Common.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Metrics;
using System.Linq;
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types;
using Ryujinx.Common.Memory;

namespace Ryujinx.RyuLDNServer
{
internal class RyuLDNServer : TcpServer
{
private RyuLdnManager _manager;

public const ushort PORT = 30456;

public RyuLDNServer() : base(IPAddress.Any, PORT)
{
_manager = new RyuLdnManager();
OptionNoDelay = true;
Logger.Info?.PrintMsg(LogClass.ServiceLdn, "RyuLDN server started");
}

protected override TcpSession CreateSession()
{
return new RyuLdnSession(this, _manager);
}

protected override void OnError(SocketError error)
{
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"Proxy TCP server caught an error with code {error}");
}
}
}
Loading

0 comments on commit 2de27b1

Please sign in to comment.