Handle NMT Login packet

This commit is contained in:
AeonLucid
2022-01-13 01:04:54 +01:00
parent 65a28b32ef
commit 7c6353c475
21 changed files with 722 additions and 57 deletions
+6
View File
@@ -0,0 +1,6 @@
namespace Prospect.Unreal.Net.Actors;
public class AActor
{
}
@@ -0,0 +1,10 @@
namespace Prospect.Unreal.Net.Actors;
public class AGameModeBase : AInfo
{
public void PreLogin(string options, string address, FUniqueNetIdRepl uniqueId, out string? errorMessage)
{
// Login unique id must match server expected unique id type OR No unique id could mean game doesn't use them
errorMessage = null;
}
}
+6
View File
@@ -0,0 +1,6 @@
namespace Prospect.Unreal.Net.Actors;
public class AInfo : AActor
{
}
@@ -0,0 +1,23 @@
namespace Prospect.Unreal.Net;
[Flags]
public enum EUniqueIdEncodingFlags
{
/** Default, nothing encoded, use normal FString serialization */
NotEncoded = 0,
/** Data is optimized based on some assumptions (even number of [0-9][a-f][A-F] that can be packed into nibbles) */
IsEncoded = (1 << 0),
/** This unique id is empty or invalid, nothing further to serialize */
IsEmpty = (1 << 1),
/** Reserved for future use */
Unused1 = (1 << 2),
/** Remaining bits are used for encoding the type without requiring another byte */
Reserved1 = (1 << 3),
Reserved2 = (1 << 4),
Reserved3 = (1 << 5),
Reserved4 = (1 << 6),
Reserved5 = (1 << 7),
/** Helper masks */
FlagsMask = (Reserved1 - 1),
TypeMask = (255 ^ FlagsMask)
}
+16
View File
@@ -0,0 +1,16 @@
namespace Prospect.Unreal.Net;
public class FUniqueNetId
{
public FUniqueNetId(string contents)
{
Contents = contents;
}
public string Contents { get; }
public bool IsValid()
{
return true;
}
}
+151 -1
View File
@@ -1,5 +1,155 @@
namespace Prospect.Unreal.Net;
using Prospect.Unreal.Core.Names;
using Prospect.Unreal.Serialization;
using Serilog;
namespace Prospect.Unreal.Net;
public class FUniqueNetIdRepl
{
private static readonly ILogger Logger = Log.ForContext<FUniqueNetIdRepl>();
private const int TypeHash_Other = 31;
public FUniqueNetId? UniqueNetId { get; private set; }
public bool IsValid()
{
return UniqueNetId != null && UniqueNetId.IsValid();
}
public string ToDebugString()
{
// TODO: Add type
return IsValid() ? $"{UniqueNetId!.Contents}" : "INVALID";
}
public static void Write(FArchive ar, FUniqueNetIdRepl value)
{
Serialize(ar, value);
}
public static FUniqueNetIdRepl Read(FArchive ar)
{
var result = new FUniqueNetIdRepl();
Serialize(ar, result);
return result;
}
private void NetSerialize(FArchive ar, UPackageMap? packageMap, out bool bOutSuccess)
{
// TODO: Get back to this later when we understand FName / FNamePool and UOnlineEngineInterface better.
// FUniqueNetIdRepl::NetSerialize
bOutSuccess = false;
if (ar.IsSaving())
{
throw new NotImplementedException();
}
else if (ar.IsLoading())
{
UniqueNetId = null;
var encodingFlags = (EUniqueIdEncodingFlags) ar.ReadByte();
if (!ar.IsError())
{
if ((encodingFlags & EUniqueIdEncodingFlags.IsEncoded) != 0)
{
if ((encodingFlags & EUniqueIdEncodingFlags.IsEmpty) == 0)
{
// Non empty and hex encoded
var typeHash = GetTypeHashFromEncoding(encodingFlags);
if (typeHash == 0)
{
// If no type was encoded, assume default
throw new NotImplementedException();
// TypeHash = UOnlineEngineInterface::Get()->GetReplicationHashForSubsystem(UOnlineEngineInterface::Get()->GetDefaultOnlineSubsystemName());
}
var bValidTypeHash = typeHash != 0;
if (typeHash == TypeHash_Other)
{
var typeString = ar.ReadString();
var type = new FName(typeString);
// TODO: Add FName into FNamePool
throw new NotImplementedException();
if (ar.IsError() || type.Number == (int)UnrealNameKey.None)
{
bValidTypeHash = false;
}
}
else
{
// Type = UOnlineEngineInterface::Get()->GetSubsystemFromReplicationHash(TypeHash);
}
throw new NotImplementedException();
}
else
{
bOutSuccess = true;
}
}
else
{
// Original FString serialization goes here
var typeHash = GetTypeHashFromEncoding(encodingFlags);
if (typeHash == 0)
{
// If no type was encoded, assume default
throw new NotImplementedException();
// TypeHash = UOnlineEngineInterface::Get()->GetReplicationHashForSubsystem(UOnlineEngineInterface::Get()->GetDefaultOnlineSubsystemName());
}
var bValidTypeHash = typeHash != 0;
if (typeHash == TypeHash_Other)
{
}
else
{
// TODO: Type = UOnlineEngineInterface::Get()->GetSubsystemFromReplicationHash(TypeHash);
}
if (bValidTypeHash)
{
var contents = ar.ReadString();
if (!ar.IsError())
{
// TODO: Check if type != none
UniqueNetId = new FUniqueNetId(contents);
bOutSuccess = true;
}
}
else
{
Logger.Warning("Error with encoded type hash");
}
}
}
else
{
Logger.Warning("Error serializing unique id");
}
}
}
private static void Serialize(FArchive ar, FUniqueNetIdRepl uniqueNetId)
{
if (!ar.IsPersistent() || ar._arIsNetArchive)
{
uniqueNetId.NetSerialize(ar, null, out _);
}
else
{
throw new NotSupportedException();
}
}
private static byte GetTypeHashFromEncoding(EUniqueIdEncodingFlags inFlags)
{
var typeHash = (byte) ((byte) (inFlags & EUniqueIdEncodingFlags.TypeMask) >> 3);
return (byte)(typeHash < 32 ? typeHash : 0);
}
}
@@ -4,7 +4,19 @@ using Serilog;
namespace Prospect.Unreal.Net.Packets.Header;
public delegate void HandlePacketNotification(SequenceNumber ackedSequence, bool delivered);
public delegate void HandlePacketNotification(PacketNotifyUpdateContext context, SequenceNumber ackedSequence, bool delivered);
public readonly ref struct PacketNotifyUpdateContext
{
public readonly HandlePacketNotification Func;
public readonly List<FChannelCloseInfo> ChannelToClose;
public PacketNotifyUpdateContext(HandlePacketNotification func, List<FChannelCloseInfo> channelToClose)
{
Func = func;
ChannelToClose = channelToClose;
}
}
public class FNetPacketNotify
{
@@ -133,14 +145,14 @@ public class FNetPacketNotify
return SequenceNumber.Diff(notificationData.Seq, _inSeq);
}
public int Update(FNotificationHeader notificationData, HandlePacketNotification func)
public int Update(FNotificationHeader notificationData, PacketNotifyUpdateContext context)
{
var inSeqDelta = GetSequenceDelta(notificationData);
if (inSeqDelta > 0)
{
Logger.Verbose("Update - Seq {Seq}, InSeq {InSeq}", notificationData.Seq.Value, _inSeq.Value);
ProcessReceivedAcks(notificationData, func);
ProcessReceivedAcks(notificationData, context);
_inSeq = notificationData.Seq;
@@ -150,7 +162,7 @@ public class FNetPacketNotify
return 0;
}
private void ProcessReceivedAcks(FNotificationHeader notificationData, HandlePacketNotification func)
private void ProcessReceivedAcks(FNotificationHeader notificationData, PacketNotifyUpdateContext context)
{
if (notificationData.AckedSeq.Greater(_outAckSeq))
{
@@ -173,7 +185,7 @@ public class FNetPacketNotify
while (ackCount > SequenceHistory.Size)
{
--ackCount;
func(currentAck, false);
context.Func(context, currentAck, false);
currentAck = currentAck.IncrementAndGet();
}
@@ -181,7 +193,7 @@ public class FNetPacketNotify
while (ackCount > 0)
{
--ackCount;
func(currentAck, notificationData.History.IsDelivered(ackCount));
context.Func(context, currentAck, notificationData.History.IsDelivered(ackCount));
currentAck = currentAck.IncrementAndGet();
}
@@ -231,7 +243,7 @@ public class FNetPacketNotify
{
while (ackedSeq.Greater(_inAckSeq))
{
_inAckSeq.IncrementAndGet();
_inAckSeq = _inAckSeq.IncrementAndGet();
var bReportAcked = _inAckSeq.Equals(ackedSeq) ? isAck : false;
@@ -246,7 +258,8 @@ public class FNetPacketNotify
// Add entry to the ack-record so that we can update the InAckSeqAck when we received the ack for this OutSeq.
_ackRecord.Enqueue(new FSentAckData(_outSeq, _writtenInAckSeq));
_writtenHistoryWordCount = 0;
_outSeq = _outSeq.IncrementAndGet();
return _outSeq.IncrementAndGet();
return _outSeq;
}
}
@@ -1,6 +0,0 @@
namespace Prospect.Unreal.Net.Player;
public class UPlayer
{
public APlayerController? PlayerController { get; set; }
}
@@ -0,0 +1,34 @@
namespace Prospect.Unreal.Net;
public class UChildConnection : UNetConnection
{
public override void LowLevelSend(byte[] data, int countBits, FOutPacketTraits traits)
{
throw new NotImplementedException();
}
public override string LowLevelGetRemoteAddress(bool bAppendPort = false)
{
throw new NotImplementedException();
}
public override string LowLevelDescribe()
{
throw new NotImplementedException();
}
public override void Tick(float deltaSeconds)
{
throw new NotImplementedException();
}
public override void CleanUp()
{
throw new NotImplementedException();
}
public override float GetTimeoutValue()
{
throw new NotImplementedException();
}
}
+127 -35
View File
@@ -1,5 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Net;
using System.Net.Sockets;
using Prospect.Unreal.Core;
using Prospect.Unreal.Core.Names;
@@ -38,7 +37,19 @@ public abstract class UNetConnection : UPlayer
public const EEngineNetworkVersionHistory DefaultEngineNetworkProtocolVersion = EEngineNetworkVersionHistory.HISTORY_ENGINENETVERSION_LATEST;
public const uint DefaultGameNetworkProtocolVersion = 0;
/// <summary>
/// The channels that need ticking. This will be a subset of OpenChannels, only including
/// channels that need to process either dormancy or queued bunches. Should be a significant
/// optimization over ticking and calling virtual functions on the potentially hundreds of
/// OpenChannels every frame.
/// </summary>
private HashSet<UChannel> _channelsToTick;
/// <summary>
/// Online platform ID of remote player on this connection. Only valid on client connections (server side).
/// </summary>
private FName _playerOnlinePlatformName;
private List<FBitReader>? _packetOrderCache;
private int _packetOrderCacheStartIdx;
private int _packetOrderCacheCount;
@@ -48,6 +59,7 @@ public abstract class UNetConnection : UPlayer
private bool _bReplay;
private double _statUpdateTime;
private double _lastReceiveTime;
private double _lastReceiveRealTime;
private double _lastGoodPacketRealtime;
@@ -73,16 +85,21 @@ public abstract class UNetConnection : UPlayer
private bool _bFlushedNetThisFrame;
private bool _bAutoFlush;
private readonly HandlePacketNotification _packetNotifyUpdateDelegate;
public UNetConnection()
{
_channelsToTick = new HashSet<UChannel>();
_playerOnlinePlatformName = UnrealNames.FNames[UnrealNameKey.None];
_packetOrderCache = null;
_packetOrderCacheStartIdx = 0;
_packetOrderCacheCount = 0;
_bFlushingPacketOrderCache = false;
_bInternalAck = false;
_bReplay = false;
_packetNotifyUpdateDelegate = PacketNotifyUpdate;
Children = new List<UChildConnection>();
Driver = null;
PackageMap = null;
OpenChannels = new List<UChannel>();
@@ -116,6 +133,11 @@ public abstract class UNetConnection : UPlayer
new SequenceNumber((ushort)OutPacketId));
}
/// <summary>
/// Child connections for secondary viewports
/// </summary>
public List<UChildConnection> Children { get; private set; }
/// <summary>
/// Owning net driver
/// </summary>
@@ -198,6 +220,11 @@ public abstract class UNetConnection : UPlayer
/// </summary>
public StatelessConnectHandlerComponent? StatelessConnectComponent { get; private set; }
/// <summary>
/// Net id of remote player on this connection. Only valid on client connections (server side).
/// </summary>
public FUniqueNetIdRepl? PlayerId { get; set; }
/// <summary>
/// Bytes overhead per packet sent.
/// </summary>
@@ -208,6 +235,21 @@ public abstract class UNetConnection : UPlayer
/// </summary>
public string Challenge { get; private set; }
/// <summary>
/// Client-generated response.
/// </summary>
public string ClientResponse { get; set; }
/// <summary>
/// The last time an ack was received
/// </summary>
public float LastRecvAckTime { get; set; }
/// <summary>
/// The last time an ack was received
/// </summary>
public double LastRecvAckTimestamp { get; set; }
/// <summary>
/// Queued up bits waiting to send
/// </summary>
@@ -321,8 +363,16 @@ public abstract class UNetConnection : UPlayer
PackageMap = packageMapClient;
}
public abstract void InitRemoteConnection(UNetDriver inDriver, UdpClient inSocket, FUrl inURL, IPEndPoint inRemoteAddr, EConnectionState inState, int inMaxPacket = 0, int inPacketOverhead = 0);
public abstract void InitLocalConnection(UNetDriver inDriver, UdpClient inSocket, FUrl inURL, EConnectionState inState, int inMaxPacket = 0, int inPacketOverhead = 0);
public virtual void InitRemoteConnection(UNetDriver inDriver, UdpClient inSocket, FUrl inURL, IPEndPoint inRemoteAddr, EConnectionState inState, int inMaxPacket = 0, int inPacketOverhead = 0)
{
throw new NotImplementedException();
}
public virtual void InitLocalConnection(UNetDriver inDriver, UdpClient inSocket, FUrl inURL, EConnectionState inState, int inMaxPacket = 0, int inPacketOverhead = 0)
{
throw new NotImplementedException();
}
public abstract void LowLevelSend(byte[] data, int countBits, FOutPacketTraits traits);
public abstract string LowLevelGetRemoteAddress(bool bAppendPort = false);
public abstract string LowLevelDescribe();
@@ -463,44 +513,25 @@ public abstract class UNetConnection : UPlayer
}
// TODO: Increment things
InPacketId += packetSequenceDelta;
}
else
{
// TODO: Increment things
// TODO: PacketOrderCache
Logger.Warning("Received out of order packet");
return;
}
// Update incoming sequence data and deliver packet notifications
// Packet is only accepted if both the incoming sequence number and incoming ack data are valid
PacketNotify.Update(header, (ackedSequence, delivered) =>
{
// TODO: Increment things
if (!new SequenceNumber((ushort)LastNotifiedPacketId).Equals(ackedSequence))
{
// TODO: Close connection.
Logger.Fatal("LastNotifiedPacketId != AckedSequence");
return;
}
if (delivered)
{
// ReceivedAck(LastNotifiedPacketId, ChannelsToClose)
Logger.Verbose("TODO: ReceivedAck");
}
else
{
// ReceivedNak(LastNotifiedPacketId)
Logger.Verbose("TODO: ReceivedNak");
}
});
PacketNotify.Update(header, new PacketNotifyUpdateContext(_packetNotifyUpdateDelegate, channelsToClose));
// Extra information associated with the header (read only after acks have been processed)
if (packetSequenceDelta > 0 && !ReadPacketInfo(reader, bHasPacketInfoPayload))
{
// TODO: Close connection.
Logger.Fatal("Failed to read PacketHeader");
Close();
return;
}
}
@@ -698,16 +729,16 @@ public abstract class UNetConnection : UPlayer
if (bunch.bReliable)
{
Logger.Verbose(" Reliable Bunch, Channel {Ch} Sequence {Seq}: Size {A.0}+{B.0}", bunch.ChIndex, bunch.ChSequence, (headerPos - incomingStartPos)/8.0f, (reader.GetPosBits()-headerPos)/8.0f);
Logger.Verbose(" Reliable Bunch, Channel {Ch} Sequence {Seq}: Size {A:###.0}+{B:###.0}", bunch.ChIndex, bunch.ChSequence, (headerPos - incomingStartPos)/8.0f, (reader.GetPosBits()-headerPos)/8.0f);
}
else
{
Logger.Verbose(" Unreliable Bunch, Channel {Ch}: Size {A.0}+{B.0}", bunch.ChIndex, (headerPos - incomingStartPos)/8.0f, (reader.GetPosBits()-headerPos)/8.0f);
Logger.Verbose(" Unreliable Bunch, Channel {Ch}: Size {A:###.0}+{B:###.0}", bunch.ChIndex, (headerPos - incomingStartPos)/8.0f, (reader.GetPosBits()-headerPos)/8.0f);
}
if (bunch.bOpen)
{
Logger.Verbose(" bOpen Bunch, Channel {Ch} Sequence {Seq}: Size {A.0}+{B.0}", bunch.ChIndex, bunch.ChSequence, (headerPos - incomingStartPos)/8.0f, (reader.GetPosBits()-headerPos)/8.0f);
Logger.Verbose(" bOpen Bunch, Channel {Ch} Sequence {Seq}: Size {A:###.0}+{B:###.0}", bunch.ChIndex, bunch.ChSequence, (headerPos - incomingStartPos)/8.0f, (reader.GetPosBits()-headerPos)/8.0f);
}
if (Channels[bunch.ChIndex] == null && (bunch.ChIndex != 0 || bunch.ChName != UnrealNames.FNames[UnrealNameKey.Control]))
@@ -902,6 +933,59 @@ public abstract class UNetConnection : UPlayer
}
}
private void PacketNotifyUpdate(PacketNotifyUpdateContext context, SequenceNumber ackedSequence, bool delivered)
{
++LastNotifiedPacketId;
// TODO: Increment things
if (!new SequenceNumber((ushort)LastNotifiedPacketId).Equals(ackedSequence))
{
Close();
Logger.Fatal("LastNotifiedPacketId != AckedSequence");
return;
}
if (delivered)
{
ReceivedAck(LastNotifiedPacketId, context.ChannelToClose);
Logger.Verbose("TODO: ReceivedAck");
}
else
{
ReceivedNak(LastNotifiedPacketId);
Logger.Verbose("TODO: ReceivedNak");
}
}
private void ReceivedAck(int ackPacketId, List<FChannelCloseInfo> outChannelsToClose)
{
Logger.Verbose("Received ack {AckPacketId}", ackPacketId);
// Advance OutAckPacketId
OutAckPacketId = ackPacketId;
// Process the bunch.
LastRecvAckTime = (float)Driver!.GetElapsedTime();
LastRecvAckTimestamp = Driver.GetElapsedTime();
if (PackageMap != null)
{
PackageMap.ReceivedAck(ackPacketId);
}
// TODO: Invoke AckChannelFunc on all channels written for this PacketId
}
private void ReceivedNak(int nakPacketId)
{
Logger.Verbose("Received nak {NakPacketId}", nakPacketId);
// Update pending NetGUIDs
PackageMap!.ReceivedNak(nakPacketId);
// TODO: Invoke NakChannelFunc on all channels written for this PacketId
}
private bool ReadPacketInfo(FBitReader reader, bool bHasPacketInfoPayload)
{
if (!bHasPacketInfoPayload)
@@ -1165,8 +1249,11 @@ public abstract class UNetConnection : UPlayer
{
// TOOD: Increment things
}
FlushNet();
if (_bAutoFlush)
{
FlushNet();
}
return bunch.PacketId;
}
@@ -1420,7 +1507,12 @@ public abstract class UNetConnection : UPlayer
NumPaddingBits = 0;
}
private void FlushNet()
public void SetPlayerOnlinePlatformName(FName inPlayerOnlinePlatformName)
{
_playerOnlinePlatformName = inPlayerOnlinePlatformName;
}
public void FlushNet(bool bIgnoreSimulation = false)
{
if (Driver == null)
{
@@ -1554,7 +1646,7 @@ public abstract class UNetConnection : UPlayer
_channelsToTick.Remove(channel);
}
private protected void SetClientLoginState(EClientLoginState newState)
internal void SetClientLoginState(EClientLoginState newState)
{
if (ClientLoginState == newState)
{
+4 -1
View File
@@ -34,8 +34,11 @@ public abstract class UNetDriver : IAsyncDisposable
}
}
// From Engine ini
// TODO: From Engine ini
public float KeepAliveTime { get; } = 0.2f;
// TODO: From Engine ini
public int MaxClientRate { get; } = 100000;
/// <summary>
/// World this net driver is associated with
+10
View File
@@ -80,4 +80,14 @@ public class UPackageMap
{
throw new NotImplementedException();
}
public void ReceivedAck(int ackPacketId)
{
// TODO: Implement
}
public void ReceivedNak(int nakPacketId)
{
// TODO: Implement
}
}