From f3b7efca451d8ef91ae2a8f091f677f61cc96f1b Mon Sep 17 00:00:00 2001 From: shalzuth Date: Sun, 16 Jan 2022 13:55:42 -0800 Subject: [PATCH] client hello (#1) --- src/Prospect.Server.Game/Client.cs | 118 +++++++ src/Prospect.Server.Game/Program.cs | 1 + src/Prospect.Unreal/Net/HandlerComponent.cs | 2 +- src/Prospect.Unreal/Net/PacketHandler.cs | 16 +- .../Net/StatelessConnectHandlerComponent.cs | 291 +++++++++++++++++- src/Prospect.Unreal/Net/UIpConnection.cs | 16 +- src/Prospect.Unreal/Net/UIpNetDriver.cs | 39 ++- src/Prospect.Unreal/Net/UNetConnection.cs | 3 +- src/Prospect.Unreal/Net/UNetDriver.cs | 18 +- src/Prospect.Unreal/Runtime/UWorld.cs | 4 +- 10 files changed, 483 insertions(+), 25 deletions(-) create mode 100644 src/Prospect.Server.Game/Client.cs diff --git a/src/Prospect.Server.Game/Client.cs b/src/Prospect.Server.Game/Client.cs new file mode 100644 index 0000000..f2cc550 --- /dev/null +++ b/src/Prospect.Server.Game/Client.cs @@ -0,0 +1,118 @@ +using Prospect.Unreal.Core; +using Prospect.Unreal.Net; +using Prospect.Unreal.Runtime; +using Prospect.Unreal.Net.Packets.Control; +using Prospect.Unreal.Net.Packets.Bunch; +using Serilog; + +namespace Prospect.Server.Game; + +internal class Client +{ + private const float TickRate = (1000.0f / 60.0f) / 1000.0f; + private readonly PeriodicTimer ClientTick = new PeriodicTimer(TimeSpan.FromSeconds(TickRate)); + UNetConnection UnitConn; + public async Task Connect(string ipAddr, int port, FUrl worldUrl) + { + var connection = new UIpNetDriver(System.Net.IPAddress.Parse(ipAddr), port, false); + await using (var world = new ProspectWorld(worldUrl)) + connection.InitConnect(world, new FUrl { Host = System.Net.IPAddress.Parse(ipAddr), Port = port }); + UnitConn = connection.ServerConnection; + connection.ServerConnection.Handler?.BeginHandshaking(SendInitialJoin); + + while (await ClientTick.WaitForNextTickAsync()) + { + if (connection != null) + { + connection.TickDispatch(TickRate); + connection.PostTickDispatch(); + + connection.TickFlush(TickRate); + connection.PostTickFlush(); + } + } + + return connection; + } + public void SendInitialJoin() + //public static void SendInitialJoin(UNetConnection UnitConn) + { + var channel = UnitConn.CreateChannelByName(new Unreal.Core.Names.FName(Unreal.Core.Names.EName.Control), Unreal.Net.Channels.EChannelCreateFlags.None, 0); + var BunchSequence = ++UnitConn.OutReliable[0]; + FOutBunch ControlChanBunch = new FOutBunch(channel, false); + ControlChanBunch.Time = 0.0; + ControlChanBunch.ReceivedAck = false; + ControlChanBunch.PacketId = 0; + //ControlChanBunch.Channel = nullptr; + ControlChanBunch.ChIndex = 0; + ControlChanBunch.ChName = new Unreal.Core.Names.FName(Unreal.Core.Names.EName.Control); + ControlChanBunch.bReliable = true; + ControlChanBunch.ChSequence = BunchSequence; + ControlChanBunch.bOpen = true; + // NOTE: Might not cover all bOpen or 'channel already open' cases + if (UnitConn.Channels[0] != null && UnitConn.Channels[0].OpenPacketId.First == 0) + { + UnitConn.Channels[0].OpenPacketId = new FPacketIdRange(BunchSequence, BunchSequence); + } + + // Need to send 'NMT_Hello' to start off the connection (the challenge is not replied to) + byte IsLittleEndian = 0; + + // We need to construct the NMT_Hello packet manually, for the initial connection + byte MessageType = (byte)NMT.Hello; + + // Allow the bunch to resize, and be split into partial bunches in SendRawBunch - for Fortnite + ControlChanBunch.SetAllowResize(true); + ControlChanBunch.WriteByte(MessageType); + ControlChanBunch.WriteByte(IsLittleEndian); + ControlChanBunch.WriteInt32(0); //FNetworkVersion::GetLocalNetworkVersion(); + // TODO hello encryption token + + /*bool bSkipControlJoin = !!(MinClientFlags & EMinClientFlags::SkipControlJoin); + bool bBeaconConnect = !!(MinClientFlags & EMinClientFlags::BeaconConnect); + + if (bBeaconConnect) + { + if (!bSkipControlJoin) + { + MessageType = NMT_BeaconJoin; + *ControlChanBunch << MessageType; + *ControlChanBunch << BeaconType; + + uint8 EncType = 0; + + *ControlChanBunch << EncType; + *ControlChanBunch << JoinUID; + + // Also immediately ack the beacon GUID setup; we're just going to let the server setup the client beacon, + // through the actor channel + MessageType = NMT_BeaconNetGUIDAck; + *ControlChanBunch << MessageType; + *ControlChanBunch << BeaconType; + } + } + else + { + // Then send NMT_Login + WriteControlLogin(ControlChanBunch); + + // Now send NMT_Join, which will spawn the PlayerController, which should then trigger replication of basic actor channels + if (!bSkipControlJoin) + { + MessageType = NMT_Join; + *ControlChanBunch << MessageType; + } + }*/ + + + UnitConn.SendRawBunch(ControlChanBunch, false); + // Immediately flush, so that Fortnite doesn't trigger an overflow + UnitConn.FlushNet(); + + + // At this point, fire of notification that we are connected + //bConnected = true; + + //ConnectedDel.ExecuteIfBound(); + } +} diff --git a/src/Prospect.Server.Game/Program.cs b/src/Prospect.Server.Game/Program.cs index 6b07714..a028e6a 100644 --- a/src/Prospect.Server.Game/Program.cs +++ b/src/Prospect.Server.Game/Program.cs @@ -36,6 +36,7 @@ internal static class Program Map = "/Game/ThirdPersonCPP/Maps/ThirdPersonExampleMap" }; + //Task.Run(() => { Thread.Sleep(100); new Client().Connect("127.0.0.1", 7777, worldUrl); }); await using (var world = new ProspectWorld(worldUrl)) { world.SetGameInstance(new UGameInstance()); diff --git a/src/Prospect.Unreal/Net/HandlerComponent.cs b/src/Prospect.Unreal/Net/HandlerComponent.cs index 5a67824..7e78009 100644 --- a/src/Prospect.Unreal/Net/HandlerComponent.cs +++ b/src/Prospect.Unreal/Net/HandlerComponent.cs @@ -70,7 +70,7 @@ public abstract class HandlerComponent public abstract void Initialize(); - public virtual void NotifiyHandshakeBegin() + public virtual void NotifyHandshakeBegin() { } diff --git a/src/Prospect.Unreal/Net/PacketHandler.cs b/src/Prospect.Unreal/Net/PacketHandler.cs index bc7b190..5f380aa 100644 --- a/src/Prospect.Unreal/Net/PacketHandler.cs +++ b/src/Prospect.Unreal/Net/PacketHandler.cs @@ -31,6 +31,7 @@ public class PacketHandler private FBitReader _incomingPacket; private bool _bBeganHandshaking; + private Action HandshakeCompleteAction; public PacketHandler() { @@ -116,15 +117,17 @@ public class PacketHandler return Outgoing_Internal(packet, countBits, traits, false, EmptyAddress); } - public void BeginHandshaking() + public void BeginHandshaking(Action handshakeComplete = null) { - // bBeganHandshaking = true; + _bBeganHandshaking = true; + + HandshakeCompleteAction = handshakeComplete; foreach (var component in _handlerComponents) { if (component.RequiresHandshake && !component.IsInitialized()) { - component.NotifiyHandshakeBegin(); + component.NotifyHandshakeBegin(); } } } @@ -324,6 +327,11 @@ public class PacketHandler } SetState(HandlerState.Initialized); + + if (_bBeganHandshaking) + { + HandshakeCompleteAction(); + } } public bool IsFullyInitialized() @@ -430,7 +438,7 @@ public class PacketHandler // (components closer to the Socket, perform their handshake first) if (_bBeganHandshaking && !curComponent.IsInitialized() && inComponent.RequiresHandshake && !bPassedHandshakeNotify && curComponent.RequiresHandshake) { - curComponent.NotifiyHandshakeBegin(); + curComponent.NotifyHandshakeBegin(); bPassedHandshakeNotify = true; } } diff --git a/src/Prospect.Unreal/Net/StatelessConnectHandlerComponent.cs b/src/Prospect.Unreal/Net/StatelessConnectHandlerComponent.cs index 162c348..196f18b 100644 --- a/src/Prospect.Unreal/Net/StatelessConnectHandlerComponent.cs +++ b/src/Prospect.Unreal/Net/StatelessConnectHandlerComponent.cs @@ -58,6 +58,36 @@ public class StatelessConnectHandlerComponent : HandlerComponent /// private int _lastClientSequence; + /// + /// The last time a handshake packet was sent - used for detecting failed sends. + /// + private double _lastClientSendTimestamp; + + /// + /// The local (client) time at which the challenge was last updated + /// + private double _lastChallengeTimestamp; + + /// + /// The local (client) time at which the last restart handshake request was receive + /// + private double _lastRestartPacketTimestamp; + + /// + /// The SecretId value of the last challenge response sent + /// + private byte _lastSecretId; + + /// + /// The Timestamp value of the last challenge response sent + /// + private double _lastTimestamp; + + /// + /// The Cookie value of the last challenge response sent. Will differ from AuthorisedCookie, if a handshake retry is triggered. + /// + private byte[] _lastCookie = new byte[CookieByteSize]; + /// /// Client: Whether or not we are in the middle of a restarted handshake. /// Server: Whether or not the last handshake was a restarted handshake. @@ -176,7 +206,136 @@ public class StatelessConnectHandlerComponent : HandlerComponent { if (Handler.Mode == HandlerMode.Client) { - throw new NotSupportedException(); + if (State == HandlerComponentState.UnInitialized || State == HandlerComponentState.InitializedOnLocal) + { + if (bRestartHandshake) + { + //UE_LOG(LogHandshake, Log, TEXT("Ignoring restart handshake request, while already restarted.")); + } + // Receiving challenge, verify the timestamp is > 0.0f + else if (timestamp > 0.0) + { + _lastChallengeTimestamp = _driver?.GetElapsedTime() ?? 0.0; + + SendChallengeResponse(secretId, timestamp, cookie); + + // Utilize this state as an intermediary, indicating that the challenge response has been sent + SetState(HandlerComponentState.InitializedOnLocal); + } + // Receiving challenge ack, verify the timestamp is < 0.0f + else if (timestamp < 0.0) + { + if (!_bRestartedHandshake) + { + var ServerConn = _driver?.ServerConnection; + + // Extract the initial packet sequence from the random Cookie data + if (ServerConn != null) + { + _lastServerSequence = BinaryPrimitives.ReadInt16LittleEndian(cookie) & (UNetConnection.MaxPacketId - 1); + _lastClientSequence = BinaryPrimitives.ReadInt16LittleEndian(cookie.Slice(2)) & (UNetConnection.MaxPacketId - 1); + + ServerConn.InitSequence(_lastServerSequence, _lastClientSequence); + } + // Save the final authorized cookie + cookie.CopyTo(_authorisedCookie); + } + + // Now finish initializing the handler - flushing the queued packet buffer in the process. + SetState(HandlerComponentState.Initialized); + Initialized(); + + _bRestartedHandshake = false; + } + } + else if (bRestartHandshake) + { + var bValidAuthCookie = !_authorisedCookie.All(b => b == 0); + + // The server has requested us to restart the handshake process - this is because + // it has received traffic from us on a different address than before. + if (bValidAuthCookie) + { + bool bPassedDelayCheck = false; + bool bPassedDualIPCheck = false; + double CurrentTime = FPlatformTime.Seconds(); + + if (!_bRestartedHandshake) + { + var ServerConn = _driver?.ServerConnection; + // todo + //double LastNetConnPacketTime = ServerConn?.lastre(ServerConn != nullptr ? ServerConn->LastReceiveRealtime : 0.0); + + // The server may send multiple restart handshake packets, so have a 10 second delay between accepting them + //bPassedDelayCheck = (CurrentTime - LastClientSendTimestamp) > 10.0; + + // Some clients end up sending packets duplicated over multiple IP's, triggering the restart handshake. + // Detect this by checking if any restart handshake requests have been received in roughly the last second + // (Dual IP situations will make the server send them constantly) - and override the checks as a failsafe, + // if no NetConnection packets have been received in the last second. + //double LastRestartPacketTimeDiff = CurrentTime - _lastRestartPacketTimestamp; + //double LastNetConnPacketTimeDiff = CurrentTime - LastNetConnPacketTime; + + //bPassedDualIPCheck = _lastRestartPacketTimestamp == 0.0 || LastRestartPacketTimeDiff > 1.1 || LastNetConnPacketTimeDiff > 1.0; + } + + _lastRestartPacketTimestamp = CurrentTime; + double LastLogStartTime = 0.0; + int LogCounter = 0; + Func WithinHandshakeLogLimit = new Func(() => { + const double LogCountPeriod = 30.0; + const byte MaxLogCount = 3; + double CurTimeApprox = _driver.GetElapsedTime(); + bool bWithinLimit = false; + if ((CurTimeApprox - LastLogStartTime) > LogCountPeriod) + { + LastLogStartTime = CurTimeApprox; + LogCounter = 1; + bWithinLimit = true; + } + else if (LogCounter < MaxLogCount) + { + LogCounter++; + bWithinLimit = true; + } + + return bWithinLimit; + }); + + if (!_bRestartedHandshake && bPassedDelayCheck && bPassedDualIPCheck) + { + Logger.Verbose("Beginning restart handshake process."); + + _bRestartedHandshake = true; + + SetState(HandlerComponentState.UnInitialized); + NotifyHandshakeBegin(); + } + else if (WithinHandshakeLogLimit()) + { + if (_bRestartedHandshake) + { + Logger.Verbose("Ignoring restart handshake request, while already restarted (this is normal)."); + } + else if (!bPassedDelayCheck) + { + Logger.Verbose("Ignoring restart handshake request, due to < 10 seconds since last handshake."); + } + else // if (!bPassedDualIPCheck) + { + Logger.Verbose("Ignoring restart handshake request, due to recent NetConnection packets."); + } + } + } + else + { + Logger.Verbose("Server sent restart handshake request, when we don't have an authorised cookie."); + } + } + else + { + // Ignore, could be a dupe/out-of-order challenge packet + } } else if (Handler.Mode == HandlerMode.Server) { @@ -248,11 +407,7 @@ public class StatelessConnectHandlerComponent : HandlerComponent if (bHandshakePacket) { - if (Handler.Mode == HandlerMode.Client) - { - throw new NotSupportedException(); - } - else if (Handler.Mode == HandlerMode.Server) + if (Handler.Mode == HandlerMode.Server) { var bInitialConnect = timestamp == 0.0; if (bInitialConnect) @@ -367,6 +522,48 @@ public class StatelessConnectHandlerComponent : HandlerComponent HMACSHA1.HashData(_handshakeSecret[secretId], writer.GetData().AsSpan((int)writer.GetNumBytes()), outCookie); } + public override void NotifyHandshakeBegin() + { + if (Handler.Mode != HandlerMode.Client) return; + + var ServerConn = _driver?.ServerConnection; + + if (_driver == null || ServerConn == null) + { + Logger.Warning("Tried to send handshake connect packet without a server connection."); + return; + } + + using var ackPacket = new FBitWriter(GetAdjustedSizeBits(HandshakePacketSizeBits) + 1); + var bHandshakePacket = (byte)1; + var bRestartHandshake = (byte)(_bRestartedHandshake ? 1 : 0); + var secretIdPad = (byte)0; + + //if (MagicHeader.Num() > 0) challengePacket.SerializeBits(MagicHeader.GetData(), MagicHeader.Num()); + + ackPacket.WriteBit(bHandshakePacket); + // In order to prevent DRDoS reflection amplification attacks, clients must pad the packet to match server packet size + ackPacket.WriteBit(bRestartHandshake); + ackPacket.WriteBit(secretIdPad); + ackPacket.Serialize(new Byte[28], 28); + + Logger.Verbose("NotifyHandshakeBegin"); + + CapHandshakePacket(ackPacket); + + // Disable PacketHandler parsing, and send the raw packet + ServerConn.Handler?.SetRawSend(true); + + if (_driver.IsNetResourceValid()) + { + ServerConn.LowLevelSend(ackPacket.GetData(), (int)ackPacket.GetNumBits(), new FOutPacketTraits()); + } + + ServerConn.Handler?.SetRawSend(false); + + _lastClientSendTimestamp = FPlatformTime.Seconds(); + } + private void SendConnectChallenge(IPEndPoint address) { if (_driver == null) @@ -410,6 +607,60 @@ public class StatelessConnectHandlerComponent : HandlerComponent connectionlessHandler?.SetRawSend(false); } + private void SendChallengeResponse(byte inSecretId, double timestamp, Span cookie) + { + var ServerConn = _driver?.ServerConnection; + + if (ServerConn == null) + { + Logger.Warning("Tried to send connect challenge without driver"); + return; + } + using var challengePacket = new FBitWriter(GetAdjustedSizeBits(_bRestartedHandshake ? RestartResponseSizeBits : HandshakePacketSizeBits) + 1); + var bHandshakePacket = (byte)1; + var bRestartHandshake = (byte)(_bRestartedHandshake ? 1 : 0); + + //if (MagicHeader.Num() > 0) + { + //challengePacket.SerializeBits(MagicHeader.GetData(), MagicHeader.Num()); + } + + challengePacket.WriteBit(bHandshakePacket); + challengePacket.WriteBit(bRestartHandshake); + challengePacket.WriteBit(inSecretId); + challengePacket.WriteDouble(timestamp); + challengePacket.Serialize(cookie, cookie.Length); + + if (_bRestartedHandshake) + { + //challengePacket.Serialize(AuthorisedCookie, COOKIE_BYTE_SIZE); + } + + Logger.Verbose("SendChallengeResponse. Timestamp: {Timestamp}, Cookie: {Cookie}", timestamp, Convert.ToHexString(cookie)); + + CapHandshakePacket(challengePacket); + + ServerConn.Handler?.SetRawSend(true); + + if (_driver.IsNetResourceValid()) + { + ServerConn.LowLevelSend(challengePacket.GetData(), (int)challengePacket.GetNumBits(), new FOutPacketTraits()); + } + + ServerConn.Handler?.SetRawSend(false); + + var CurSequence = cookie; + + _lastClientSendTimestamp = FPlatformTime.Seconds(); + _lastSecretId = inSecretId; + _lastTimestamp = timestamp; + + _lastServerSequence = BinaryPrimitives.ReadInt16LittleEndian(cookie) & (UNetConnection.MaxPacketId - 1); + _lastClientSequence = BinaryPrimitives.ReadInt16LittleEndian(cookie.Slice(2)) & (UNetConnection.MaxPacketId - 1); + + _lastCookie = cookie.ToArray(); + } + private void SendChallengeAck(IPEndPoint address, byte[] inCookie) { if (_driver == null) @@ -564,7 +815,33 @@ public class StatelessConnectHandlerComponent : HandlerComponent { if (Handler.Mode == HandlerMode.Client) { - throw new NotImplementedException(); + if (State != HandlerComponentState.Initialized && _lastClientSendTimestamp != 0.0) + { + double LastSendTimeDiff = FPlatformTime.Seconds() - _lastClientSendTimestamp; + + if (LastSendTimeDiff > 1.0) + { + var bRestartChallenge = _driver != null && ((_driver.GetElapsedTime() - _lastChallengeTimestamp) > MinCookieLifetime); + + if (bRestartChallenge) + { + SetState(HandlerComponentState.UnInitialized); + } + + if (State == HandlerComponentState.UnInitialized) + { + Logger.Verbose("Initial handshake packet timeout - resending."); + + NotifyHandshakeBegin(); + } + else if (State == HandlerComponentState.InitializedOnLocal && _lastTimestamp != 0.0) + { + Logger.Verbose("Challenge response packet timeout - resending."); + + SendChallengeResponse(_lastSecretId, _lastTimestamp, _lastCookie); + } + } + } } else { diff --git a/src/Prospect.Unreal/Net/UIpConnection.cs b/src/Prospect.Unreal/Net/UIpConnection.cs index adeb57d..8066731 100644 --- a/src/Prospect.Unreal/Net/UIpConnection.cs +++ b/src/Prospect.Unreal/Net/UIpConnection.cs @@ -51,7 +51,12 @@ public class UIpConnection : UNetConnection public override void InitLocalConnection(UNetDriver inDriver, UdpClient inSocket, FUrl inURL, EConnectionState inState, int inMaxPacket = 0, int inPacketOverhead = 0) { - throw new NotSupportedException(); + InitBase(inDriver, inSocket, inURL, inState, + // Use the default packet size/overhead unless overridden by a child class + (inMaxPacket == 0 || inMaxPacket > MaxPacketSize) ? MaxPacketSize : inMaxPacket, + inPacketOverhead == 0 ? inMaxPacket : inPacketOverhead); + //RemoteAddr = new IPEndPoint(inURL.Host, inURL.Port); + InitSendBuffer(); } public override void LowLevelSend(byte[] data, int countBits, FOutPacketTraits traits) @@ -82,8 +87,13 @@ public class UIpConnection : UNetConnection { throw new UnrealNetException(); } - - Socket.Send(dataToSend, countBytes, RemoteAddr); + + // can't send remote addr if client is already set. receive is called before now too, so need to establish socket before then. + if (RemoteAddr.Address.GetAddressBytes().All(a => a == 0xff)) + Socket.Send(dataToSend, countBytes); + + else + Socket.Send(dataToSend, countBytes, RemoteAddr); } } diff --git a/src/Prospect.Unreal/Net/UIpNetDriver.cs b/src/Prospect.Unreal/Net/UIpNetDriver.cs index 99c2f85..68663bf 100644 --- a/src/Prospect.Unreal/Net/UIpNetDriver.cs +++ b/src/Prospect.Unreal/Net/UIpNetDriver.cs @@ -11,10 +11,18 @@ public class UIpNetDriver : UNetDriver private bool _isDisposed; - public UIpNetDriver(IPAddress host, int port) + public UIpNetDriver(IPAddress host, int port, bool server) { - ServerIp = new IPEndPoint(host, port); - Socket = new UdpClient(ServerIp); + if (server) + { + ServerIp = new IPEndPoint(host, port); + Socket = new UdpClient(ServerIp); // binds to local + } + else + { + Socket = new UdpClient(host.ToString(), port); // binds to remote, establishes local port + ServerIp = new IPEndPoint(host, ((IPEndPoint)Socket.Client.LocalEndPoint).Port); + } ReceiveThread = new FReceiveThreadRunnable(this); } @@ -22,9 +30,28 @@ public class UIpNetDriver : UNetDriver public UdpClient Socket { get; } public FReceiveThreadRunnable ReceiveThread { get; } - public override bool Init(FNetworkNotify notify) + public bool InitConnect(FNetworkNotify InNotify, FUrl ConnectURL) { - if (!base.Init(notify)) + if (!Init(InNotify)) + { + Logger.Warning("Failed to init net driver ConnectURL: %s", ConnectURL.ToString()); + return false; + } + + // Create new connection. + var ipConnection = new UIpConnection(); + ServerConnection = ipConnection; + ServerConnection.InitLocalConnection(this, Socket, ConnectURL, EConnectionState.USOCK_Pending); + int DestinationPort = ConnectURL.Port; + Logger.Warning("Game client on port %i, rate %i", DestinationPort, ServerConnection.CurrentNetSpeed); + CreateInitialClientChannels(); + ReceiveThread.Start(); + return true; + } + + public bool InitListen(FNetworkNotify notify) + { + if (!Init(notify)) { return false; } @@ -46,7 +73,7 @@ public class UIpNetDriver : UNetDriver { Logger.Information("Received from {Adress} data {Buffer}", packet.Address, packet.DataView.GetData()); - UNetConnection? connection = null; + UNetConnection? connection = ServerConnection; if (Equals(packet.Address, ServerIp)) { diff --git a/src/Prospect.Unreal/Net/UNetConnection.cs b/src/Prospect.Unreal/Net/UNetConnection.cs index c6b9fb0..9f60333 100644 --- a/src/Prospect.Unreal/Net/UNetConnection.cs +++ b/src/Prospect.Unreal/Net/UNetConnection.cs @@ -1123,7 +1123,8 @@ public abstract class UNetConnection : UPlayer } Handler = new PacketHandler(); - Handler.Initialize(HandlerMode.Server /* Mode */, UNetConnection.MaxPacketSize * 8 ,false); + var mode = Driver.ServerConnection != null ? HandlerMode.Client : HandlerMode.Server; + Handler.Initialize(mode, UNetConnection.MaxPacketSize * 8 ,false); StatelessConnectComponent = (StatelessConnectHandlerComponent) Handler.AddHandler(); StatelessConnectComponent.SetDriver(Driver); diff --git a/src/Prospect.Unreal/Net/UNetDriver.cs b/src/Prospect.Unreal/Net/UNetDriver.cs index 1523c37..2d8a29d 100644 --- a/src/Prospect.Unreal/Net/UNetDriver.cs +++ b/src/Prospect.Unreal/Net/UNetDriver.cs @@ -59,6 +59,11 @@ public abstract class UNetDriver : IAsyncDisposable /// public Dictionary ChannelDefinitionMap { get; } + /// + /// AConnection to the server (this net driver is a client) + /// + public UNetConnection ServerConnection { get; set; } + /// /// Array of connections to clients (this net driver is a host) - unsorted, and ordering changes depending on actor replication /// @@ -184,7 +189,7 @@ public abstract class UNetDriver : IAsyncDisposable public bool IsServer() { - return true; + return ServerConnection == null; } public bool IsKnownChannelName(FName name) @@ -223,6 +228,17 @@ public abstract class UNetDriver : IAsyncDisposable // TODO: NetworkObjectList > HandleConnectionAdded } + protected void CreateInitialClientChannels() + { + foreach (var channelDef in ChannelDefinitions) + { + if (channelDef.InitialServer) + { + ServerConnection.CreateChannelByName(channelDef.Name, EChannelCreateFlags.OpenedLocally, channelDef.StaticChannelIndex); + } + } + } + private void CreateInitialServerChannels(UNetConnection clientConnection) { foreach (var channelDef in ChannelDefinitions) diff --git a/src/Prospect.Unreal/Runtime/UWorld.cs b/src/Prospect.Unreal/Runtime/UWorld.cs index b5ef4a1..9a141a7 100644 --- a/src/Prospect.Unreal/Runtime/UWorld.cs +++ b/src/Prospect.Unreal/Runtime/UWorld.cs @@ -86,10 +86,10 @@ public abstract partial class UWorld : FNetworkNotify, IAsyncDisposable return false; } - NetDriver = new UIpNetDriver(Url.Host, Url.Port); + NetDriver = new UIpNetDriver(Url.Host, Url.Port, IsServer()); NetDriver.SetWorld(this); - if (!NetDriver.Init(this)) + if (!((UIpNetDriver)NetDriver).InitListen(this)) { Logger.Error("Failed to listen"); NetDriver = null;