Started serializing bunches

This commit is contained in:
AeonLucid
2022-01-10 04:38:06 +01:00
parent 49e6e0f6f8
commit f3b0a31761
36 changed files with 2013 additions and 54 deletions
@@ -1,6 +1,6 @@
namespace Prospect.Unreal.Core;
public enum EEngineNetworkVersionHistory
public enum EEngineNetworkVersionHistory : uint
{
HISTORY_INITIAL = 1,
HISTORY_REPLAY_BACKWARDS_COMPAT = 2, // Bump version to get rid of older replays before backwards compat was turned on officially
+7 -7
View File
@@ -4,11 +4,11 @@ namespace Prospect.Unreal.Core;
public class FUrl
{
public string Protocol { get; init; } = "unreal";
public IPAddress Host { get; init; } = IPAddress.Any;
public int Port { get; init; } = 7777;
public string Map { get; init; } = "GearStart";
public string RedirectUrl { get; init; } = string.Empty;
public List<string> Options { get; init; } = new List<string>();
public string Portal { get; init; } = string.Empty;
public string Protocol { get; set; } = "unreal";
public IPAddress Host { get; set; } = IPAddress.Any;
public int Port { get; set; } = 7777;
public string Map { get; set; } = "GearStart";
public string RedirectUrl { get; set; } = string.Empty;
public List<string> Options { get; set; } = new List<string>();
public string Portal { get; set; } = string.Empty;
}
+36
View File
@@ -0,0 +1,36 @@
namespace Prospect.Unreal.Core.Names;
public class FName
{
public FName()
{
NameIndex = -1; // ?
Number = -1; // ?
Str = "None";
}
public FName(string str)
{
NameIndex = -1;
Number = -1;
Str = str;
}
public FName(string str, int number)
{
NameIndex = -1;
Number = number;
Str = str;
}
public Int32 NameIndex { get; set; }
public Int32 Number { get; set; }
public string Str { get; set; }
public override string ToString()
{
return Str;
}
}
@@ -0,0 +1,202 @@
namespace Prospect.Unreal.Core.Names
{
public enum UnrealNameKey
{
None = 0,
// Class property types (name indices are significant for serialization).
ByteProperty = 1,
IntProperty = 2,
BoolProperty = 3,
FloatProperty = 4,
ObjectProperty = 5, // ClassProperty shares the same tag
NameProperty = 6,
DelegateProperty = 7,
DoubleProperty = 8,
ArrayProperty = 9,
StructProperty = 10,
VectorProperty = 11,
RotatorProperty = 12,
StrProperty = 13,
TextProperty = 14,
InterfaceProperty = 15,
MulticastDelegateProperty = 16,
//Available = 17,
LazyObjectProperty = 18,
SoftObjectProperty = 19, // SoftClassProperty shares the same tag
UInt64Property = 20,
UInt32Property = 21,
UInt16Property = 22,
Int64Property = 23,
Int16Property = 25,
Int8Property = 26,
//Available = 27,
MapProperty = 28,
SetProperty = 29,
// Special packages.
Core = 30,
Engine = 31,
Editor = 32,
CoreUObject = 33,
// More class properties
EnumProperty = 34,
// Special types.
Cylinder = 50,
BoxSphereBounds = 51,
Sphere = 52,
Box = 53,
Vector2D = 54,
IntRect = 55,
IntPoint = 56,
Vector4 = 57,
Name = 58,
Vector = 59,
Rotator = 60,
SHVector = 61,
Color = 62,
Plane = 63,
Matrix = 64,
LinearColor = 65,
AdvanceFrame = 66,
Pointer = 67,
Double = 68,
Quat = 69,
Self = 70,
Transform = 71,
// Object class names.
Object = 100,
Camera = 101,
Actor = 102,
ObjectRedirector = 103,
ObjectArchetype = 104,
Class = 105,
ScriptStruct = 106,
Function = 107,
// Misc.
State = 200,
TRUE = 201,
FALSE = 202,
Enum = 203,
Default = 204,
Skip = 205,
Input = 206,
Package = 207,
Groups = 208,
Interface = 209,
Components = 210,
Global = 211,
Super = 212,
Outer = 213,
Map = 214,
Role = 215,
RemoteRole = 216,
PersistentLevel = 217,
TheWorld = 218,
PackageMetaData = 219,
InitialState = 220,
Game = 221,
SelectionColor = 222,
UI = 223,
ExecuteUbergraph = 224,
DeviceID = 225,
RootStat = 226,
MoveActor = 227,
All = 230,
MeshEmitterVertexColor = 231,
TextureOffsetParameter = 232,
TextureScaleParameter = 233,
ImpactVel = 234,
SlideVel = 235,
TextureOffset1Parameter = 236,
MeshEmitterDynamicParameter = 237,
ExpressionInput = 238,
Untitled = 239,
Timer = 240,
Team = 241,
Low = 242,
High = 243,
NetworkGUID = 244,
GameThread = 245,
RenderThread = 246,
OtherChildren = 247,
Location = 248,
Rotation = 249,
BSP = 250,
EditorSettings = 251,
AudioThread = 252,
ID = 253,
UserDefinedEnum = 254,
Control = 255,
Voice = 256,
Zlib = 257,
Gzip = 258,
// Online
DGram = 280,
Stream = 281,
GameNetDriver = 282,
PendingNetDriver = 283,
BeaconNetDriver = 284,
FlushNetDormancy = 285,
DemoNetDriver = 286,
GameSession = 287,
PartySession = 288,
GamePort = 289,
BeaconPort = 290,
MeshPort = 291,
MeshNetDriver = 292,
// Texture settings.
Linear = 300,
Point = 301,
Aniso = 302,
LightMapResolution = 303,
// Sound.
// = 310,
UnGrouped = 311,
VoiceChat = 312,
// Optimized replication.
Playing = 320,
Spectating = 322,
Inactive = 325,
// Log messages.
PerfWarning = 350,
Info = 351,
Init = 352,
Exit = 353,
Cmd = 354,
Warning = 355,
Error = 356,
// File format backwards-compatibility.
FontCharacter = 400,
InitChild2StartBone = 401,
SoundCueLocalized = 402,
SoundCue = 403,
RawDistributionFloat = 404,
RawDistributionVector = 405,
InterpCurveFloat = 406,
InterpCurveVector2D = 407,
InterpCurveVector = 408,
InterpCurveTwoVectors = 409,
InterpCurveQuat = 410,
AI = 450,
NavMesh = 451,
PerformanceCapture = 500,
// Special config names - not required to be consistent for network replication
EditorLayout = 600,
EditorKeyBindings = 601,
GameUserSettings = 602
}
}
@@ -0,0 +1,231 @@
namespace Prospect.Unreal.Core.Names
{
public static class UnrealNames
{
public const int MaxNetworkedHardcodedName = 410;
static UnrealNames()
{
// ReSharper disable once UseObjectOrCollectionInitializer
var names = new Dictionary<int, string>();
// Special zero value, meaning no name.
names.Add(0, "None");
// Class property types (name indices are significant for serialization).
names.Add(1, "ByteProperty");
names.Add(2, "IntProperty");
names.Add(3, "BoolProperty");
names.Add(4, "FloatProperty");
names.Add(5, "ObjectProperty"); // ClassProperty shares the same tag
names.Add(6, "NameProperty");
names.Add(7, "DelegateProperty");
names.Add(8, "DoubleProperty");
names.Add(9, "ArrayProperty");
names.Add(10, "StructProperty");
names.Add(11, "VectorProperty");
names.Add(12, "RotatorProperty");
names.Add(13, "StrProperty");
names.Add(14, "TextProperty");
names.Add(15, "InterfaceProperty");
names.Add(16, "MulticastDelegateProperty");
//Names.Add(17, "Available");
names.Add(18, "LazyObjectProperty");
names.Add(19, "SoftObjectProperty"); // SoftClassProperty shares the same tag
names.Add(20, "UInt64Property");
names.Add(21, "UInt32Property");
names.Add(22, "UInt16Property");
names.Add(23, "Int64Property");
names.Add(25, "Int16Property");
names.Add(26, "Int8Property");
//Names.Add(27, "Available");
names.Add(28, "MapProperty");
names.Add(29, "SetProperty");
// Special packages.
names.Add(30, "Core");
names.Add(31, "Engine");
names.Add(32, "Editor");
names.Add(33, "CoreUObject");
// More class properties
names.Add(34, "EnumProperty");
// Special types.
names.Add(50, "Cylinder");
names.Add(51, "BoxSphereBounds");
names.Add(52, "Sphere");
names.Add(53, "Box");
names.Add(54, "Vector2D");
names.Add(55, "IntRect");
names.Add(56, "IntPoint");
names.Add(57, "Vector4");
names.Add(58, "Name");
names.Add(59, "Vector");
names.Add(60, "Rotator");
names.Add(61, "SHVector");
names.Add(62, "Color");
names.Add(63, "Plane");
names.Add(64, "Matrix");
names.Add(65, "LinearColor");
names.Add(66, "AdvanceFrame");
names.Add(67, "Pointer");
names.Add(68, "Double");
names.Add(69, "Quat");
names.Add(70, "Self");
names.Add(71, "Transform");
// Object class names.
names.Add(100, "Object");
names.Add(101, "Camera");
names.Add(102, "Actor");
names.Add(103, "ObjectRedirector");
names.Add(104, "ObjectArchetype");
names.Add(105, "Class");
names.Add(106, "ScriptStruct");
names.Add(107, "Function");
names.Add(108, "Pawn");
// Misc.
names.Add(200, "State");
names.Add(201, "TRUE");
names.Add(202, "FALSE");
names.Add(203, "Enum");
names.Add(204, "Default");
names.Add(205, "Skip");
names.Add(206, "Input");
names.Add(207, "Package");
names.Add(208, "Groups");
names.Add(209, "Interface");
names.Add(210, "Components");
names.Add(211, "Global");
names.Add(212, "Super");
names.Add(213, "Outer");
names.Add(214, "Map");
names.Add(215, "Role");
names.Add(216, "RemoteRole");
names.Add(217, "PersistentLevel");
names.Add(218, "TheWorld");
names.Add(219, "PackageMetaData");
names.Add(220, "InitialState");
names.Add(221, "Game");
names.Add(222, "SelectionColor");
names.Add(223, "UI");
names.Add(224, "ExecuteUbergraph");
names.Add(225, "DeviceID");
names.Add(226, "RootStat");
names.Add(227, "MoveActor");
names.Add(230, "All");
names.Add(231, "MeshEmitterVertexColor");
names.Add(232, "TextureOffsetParameter");
names.Add(233, "TextureScaleParameter");
names.Add(234, "ImpactVel");
names.Add(235, "SlideVel");
names.Add(236, "TextureOffset1Parameter");
names.Add(237, "MeshEmitterDynamicParameter");
names.Add(238, "ExpressionInput");
names.Add(239, "Untitled");
names.Add(240, "Timer");
names.Add(241, "Team");
names.Add(242, "Low");
names.Add(243, "High");
names.Add(244, "NetworkGUID");
names.Add(245, "GameThread");
names.Add(246, "RenderThread");
names.Add(247, "OtherChildren");
names.Add(248, "Location");
names.Add(249, "Rotation");
names.Add(250, "BSP");
names.Add(251, "EditorSettings");
names.Add(252, "AudioThread");
names.Add(253, "ID");
names.Add(254, "UserDefinedEnum");
names.Add(255, "Control");
names.Add(256, "Voice");
names.Add(257, " Zlib");
names.Add(258, " Gzip");
names.Add(259, " LZ4");
names.Add(260, " Mobile");
// Online
names.Add(280, "DGram");
names.Add(281, "Stream");
names.Add(282, "GameNetDriver");
names.Add(283, "PendingNetDriver");
names.Add(284, "BeaconNetDriver");
names.Add(285, "FlushNetDormancy");
names.Add(286, "DemoNetDriver");
names.Add(287, "GameSession");
names.Add(288, "PartySession");
names.Add(289, "GamePort");
names.Add(290, "BeaconPort");
names.Add(291, "MeshPort");
names.Add(292, "MeshNetDriver");
names.Add(293, "LiveStreamVoice");
names.Add(294, "LiveStreamAnimation");
// Texture settings.
names.Add(300, "Linear");
names.Add(301, "Point");
names.Add(302, "Aniso");
names.Add(303, "LightMapResolution");
// Sound.
//Names.Add(310, "");
names.Add(311, "UnGrouped");
names.Add(312, "VoiceChat");
// Optimized replication.
names.Add(320, "Playing");
names.Add(322, "Spectating");
names.Add(325, "Inactive");
// Log messages.
names.Add(350, "PerfWarning");
names.Add(351, "Info");
names.Add(352, "Init");
names.Add(353, "Exit");
names.Add(354, "Cmd");
names.Add(355, "Warning");
names.Add(356, "Error");
// File format backwards-compatibility.
names.Add(400, "FontCharacter");
names.Add(401, "InitChild2StartBone");
names.Add(402, "SoundCueLocalized");
names.Add(403, "SoundCue");
names.Add(404, "RawDistributionFloat");
names.Add(405, "RawDistributionVector");
names.Add(406, "InterpCurveFloat");
names.Add(407, "InterpCurveVector2D");
names.Add(408, "InterpCurveVector");
names.Add(409, "InterpCurveTwoVectors");
names.Add(410, "InterpCurveQuat");
names.Add(450, "AI");
names.Add(451, "NavMesh");
names.Add(500, "PerformanceCapture");
// Special config names - not required to be consistent for network replication
names.Add(600, "EditorLayout");
names.Add(601, "EditorKeyBindings");
names.Add(602, "GameUserSettings");
names.Add(700, "Filename");
names.Add(701, "Lerp");
names.Add(702, "Root");
// Save.
Names = names;
FNames = Names.ToDictionary(x => (UnrealNameKey) x.Key, y => new FName(y.Value, y.Key));
MaxHardcodedNameIndex = Names.Last().Key + 1;
}
public static IReadOnlyDictionary<int, string> Names { get; }
public static IReadOnlyDictionary<UnrealNameKey, FName> FNames { get; }
public static int MaxHardcodedNameIndex { get; }
}
}
@@ -0,0 +1,12 @@
namespace Prospect.Unreal.Net.Channels;
public enum EChannelCloseReason
{
Destroyed,
Dormancy,
LevelUnloaded,
Relevancy,
TearOff,
/* reserved */
MAX = 15 // this value is used for serialization, modifying it may require a network version change
}
@@ -0,0 +1,8 @@
namespace Prospect.Unreal.Net.Channels;
[Flags]
public enum EChannelCreateFlags
{
None = (1 << 0),
OpenedLocally = (1 << 1)
}
@@ -0,0 +1,13 @@
namespace Prospect.Unreal.Net.Channels;
public static class EChannelType
{
public const int CHTYPE_None = 0; // Invalid type.
public const int CHTYPE_Control = 1; // Connection control.
public const int CHTYPE_Actor = 2; // Actor-update channel.
public const int CHTYPE_File = 3; // Binary file transfer.
public const int CHTYPE_Voice = 4; // VoIP data channel
public const int CHTYPE_MAX = 8; // Maximum.
}
-6
View File
@@ -1,6 +0,0 @@
namespace Prospect.Unreal.Net;
internal class CoreNet
{
public const int MaxPacketSize = 1024;
}
@@ -0,0 +1,10 @@
namespace Prospect.Unreal.Net;
public enum EClientLoginState
{
Invalid = 0, // This must be a client (which doesn't use this state) or uninitialized.
LoggingIn = 1, // The client is currently logging in.
Welcomed = 2, // Told client to load map and will respond with SendJoin
ReceivedJoin = 3, // NMT_Join received and a player controller has been created
CleanedUp = 4 // Cleanup has been called at least once, the connection is considered abandoned/terminated/gone
}
+7
View File
@@ -0,0 +1,7 @@
namespace Prospect.Unreal.Net;
public enum ECountUnits
{
Bits,
Bytes
}
@@ -0,0 +1,3 @@
namespace Prospect.Unreal.Net;
public record FChannelDefinition(string Name, Type Class, int StaticChannelIndex, bool TickOnCreate, bool ServerOpen, bool ClientOpen, bool InitialServer, bool InitialClient);
+11
View File
@@ -0,0 +1,11 @@
namespace Prospect.Unreal.Net;
public class FNetGUIDCache
{
public FNetGUIDCache(UNetDriver driver)
{
Driver = driver;
}
public UNetDriver Driver { get; }
}
@@ -34,7 +34,7 @@ public class FReceiveThreadRunnable : IAsyncDisposable
if (_receiveQueue.TryDequeue(out var packet))
{
result = new FReceivedPacketView(
new FPacketDataView(packet.Buffer, packet.Buffer.Length),
new FPacketDataView(packet.Buffer, packet.Buffer.Length, ECountUnits.Bytes),
packet.Address,
new FInPacketTraits());
@@ -58,10 +58,10 @@ public class FReceiveThreadRunnable : IAsyncDisposable
continue;
}
if (result.Buffer.Length > CoreNet.MaxPacketSize)
if (result.Buffer.Length > UNetConnection.MaxPacketSize)
{
Logger.Warning("Received packet exceeding MaxPacketSize ({Size} > {Max}) from {Ip}",
result.Buffer.Length, CoreNet.MaxPacketSize, result.RemoteEndPoint);
result.Buffer.Length, UNetConnection.MaxPacketSize, result.RemoteEndPoint);
continue;
}
+29 -2
View File
@@ -50,7 +50,11 @@ public class PacketHandler
if (!_bConnectionlessHandler)
{
throw new NotImplementedException();
// TODO: Load from .ini file (GEngineIni)
// %s PacketHandlerProfileConfig (Driver..)
// Components
// If no matches, load from PacketHandlerComponents / Components in GEngineIni
}
// TODO: FEncryptionComponent.
@@ -93,6 +97,19 @@ public class PacketHandler
return Outgoing_Internal(packet, countBits, traits, true, address);
}
public void BeginHandshaking()
{
// bBeganHandshaking = true;
foreach (var component in _handlerComponents)
{
if (component.RequiresHandshake() && !component.IsInitialized())
{
component.NotifiyHandshakeBegin();
}
}
}
public HandlerComponent AddHandler<T>() where T : HandlerComponent
{
var result = (HandlerComponent) Activator.CreateInstance(typeof(T), this)!;
@@ -102,6 +119,16 @@ public class PacketHandler
return result;
}
public bool Incoming(FReceivedPacketView packetView)
{
return Incoming_Internal(packetView);
}
public void IncomingHigh(FBitReader reader)
{
// NO-OP
}
private bool Incoming_Internal(FReceivedPacketView packetView)
{
var returnVal = true;
@@ -159,7 +186,7 @@ public class PacketHandler
{
ReplaceIncomingPacket(processPacketReader);
packetView.DataView = new FPacketDataView(_incomingPacket.GetBuffer(), _incomingPacket.GetBitsLeft());
packetView.DataView = new FPacketDataView(_incomingPacket.GetBuffer(), _incomingPacket.GetBitsLeft(), ECountUnits.Bits);
}
else
{
+16 -4
View File
@@ -1,4 +1,5 @@
using System.Net;
using Prospect.Unreal.Core;
namespace Prospect.Unreal.Net;
@@ -8,15 +9,26 @@ public class FInPacketTraits
public bool FromRecentlyDisconnected { get; set; }
}
public class FPacketDataView
public readonly struct FPacketDataView
{
private readonly byte[] _data;
private readonly int _count;
private readonly int _countBits;
public FPacketDataView(byte[] data, int length)
public FPacketDataView(byte[] data, int length, ECountUnits unit)
{
_data = data;
_countBits = length * 8;
if (unit == ECountUnits.Bits)
{
_count = FMath.DivideAndRoundUp(length, 8);
_countBits = length;
}
else /* if (unit == ECountUnits.Bytes) */
{
_count = length;
_countBits = length * 8;
}
}
public byte[] GetData()
@@ -31,7 +43,7 @@ public class FPacketDataView
public int NumBytes()
{
return _data.Length;
return _count;
}
}
@@ -0,0 +1,95 @@
using Prospect.Unreal.Core.Names;
using Prospect.Unreal.Net.Channels;
using Prospect.Unreal.Serialization;
namespace Prospect.Unreal.Net.Packets.Bunch
{
public class FInBunch : FNetBitReader
{
public FInBunch(UNetConnection inConnection, byte[]? src = null, int countBits = 0) : base(inConnection.PackageMap, src, countBits)
{
PacketId = 0;
Next = null;
Connection = inConnection;
ChIndex = 0;
ChType = 0; // TODO: CHTYPE_None
ChName = UnrealNames.FNames[UnrealNameKey.None]; // TODO: NAME_None
ChSequence = 0;
bOpen = false;
bClose = false;
bDormant = false;
bReliable = false;
bPartial = false;
bPartialInitial = false;
bPartialFinal = false;
bHasPackageMapExports = false;
bHasMustBeMappedGUIDs = false;
bIgnoreRPCs = false;
CloseReason = EChannelCloseReason.Destroyed;
// TODO: SetByteSwapping(Connection->bNeedsByteSwapping);
SetEngineNetVer(Connection.EngineNetworkProtocolVersion);
SetGameNetVer(Connection.GameNetworkProtocolVersion);
}
public int PacketId { get; set; }
public FInBunch? Next { get; set; }
public UNetConnection Connection { get; }
public int ChIndex { get; set; }
public int ChType { get; set; }
public FName ChName { get; set; }
public int ChSequence { get; set; }
public bool bOpen { get; set; }
public bool bClose { get; set; }
/// <summary>
/// Close, but go dormant.
/// </summary>
public bool bDormant { get; set; }
/// <summary>
/// Replication on this channel is being paused by the server.
/// </summary>
public bool bIsReplicationPaused { get; set; }
public bool bReliable { get; set; }
/// <summary>
/// Not a complete bunch
/// </summary>
public bool bPartial { get; set; }
/// <summary>
/// The first bunch of a partial bunch
/// </summary>
public bool bPartialInitial { get; set; }
/// <summary>
/// The final bunch of a partial bunch
/// </summary>
public bool bPartialFinal { get; set; }
/// <summary>
/// This bunch has networkGUID name/id pairs.
/// </summary>
public bool bHasPackageMapExports { get; set; }
/// <summary>
/// This bunch has guids that must be mapped before we can process this bunch.
/// </summary>
public bool bHasMustBeMappedGUIDs { get; set; }
public bool bIgnoreRPCs { get; set; }
public EChannelCloseReason CloseReason { get; set; }
}
}
@@ -0,0 +1,146 @@
using Prospect.Unreal.Net.Packets.Header.Sequence;
using Prospect.Unreal.Serialization;
using Serilog;
namespace Prospect.Unreal.Net.Packets.Header;
public delegate void HandlePacketNotification(SequenceNumber ackedSequence, bool delivered);
public class FNetPacketNotify
{
private static readonly ILogger Logger = Log.ForContext<FNetPacketNotify>();
public const int SequenceNumberBits = 14;
public const int MaxSequenceHistoryLength = 256;
private readonly Queue<FSentAckData> _ackRecord = new Queue<FSentAckData>();
private readonly SequenceHistory _inSeqHistory = new SequenceHistory();
private SequenceNumber _inSeq;
private SequenceNumber _inAckSeq;
private SequenceNumber _inAckSeqAck;
private SequenceNumber _outSeq;
private SequenceNumber _outAckSeq;
public void Init(SequenceNumber initialInSeq, SequenceNumber initialOutSeq)
{
_inSeqHistory.Reset();
_inSeq = initialInSeq;
_inAckSeq = initialInSeq;
_inAckSeqAck = initialInSeq;
_outSeq = initialOutSeq;
_outAckSeq = new SequenceNumber((ushort)(initialOutSeq.Value - 1));
}
public bool ReadHeader(ref FNotificationHeader data, FBitReader reader)
{
var packedHeader = reader.ReadUInt32();
data.Seq = FPackedHeader.GetSeq(packedHeader);
data.AckedSeq = FPackedHeader.GetAckedSeq(packedHeader);
data.HistoryWordCount = FPackedHeader.GetHistoryWordCount(packedHeader) + 1;
data.History = new SequenceHistory();
data.History.Read(reader, data.HistoryWordCount);
return !reader.IsError();
}
public int GetSequenceDelta(FNotificationHeader notificationData)
{
if (!notificationData.Seq.Greater(_inSeq))
{
return 0;
}
if (!notificationData.AckedSeq.GreaterEq(_outAckSeq))
{
return 0;
}
if (!_outSeq.Greater(notificationData.AckedSeq))
{
return 0;
}
return SequenceNumber.Diff(notificationData.Seq, _inSeq);
}
public int Update(FNotificationHeader notificationData, HandlePacketNotification func)
{
var inSeqDelta = GetSequenceDelta(notificationData);
if (inSeqDelta > 0)
{
Logger.Verbose("FNetPacketNotify::Update - Seq {Seq}, InSeq {InSeq}", notificationData.Seq.Value, _inSeq.Value);
ProcessReceivedAcks(notificationData, func);
_inSeq = notificationData.Seq;
return inSeqDelta;
}
return 0;
}
private void ProcessReceivedAcks(FNotificationHeader notificationData, HandlePacketNotification func)
{
if (notificationData.AckedSeq.Greater(_outAckSeq))
{
Logger.Verbose("ProcessReceivedAcks - AckedSeq {AckedSeq}, OutAckSeq {OutAckSeq}", notificationData.AckedSeq.Value, _outAckSeq.Value);
var ackCount = SequenceNumber.Diff(notificationData.AckedSeq, _outAckSeq);
// Update InAckSeqAck used to track the needed number of bits to transmit our ack history
_inAckSeqAck = UpdateInAckSeqAck(ackCount, notificationData.AckedSeq);
// ExpectedAck = OutAckSeq + 1
var currentAck = new SequenceNumber(_outAckSeq.Value).IncrementAndGet();
if (ackCount > SequenceHistory.Size)
{
Logger.Warning("ProcessReceivedAcks - Missed Acks");
}
// Everything not found in the history buffer is treated as lost
while (ackCount > SequenceHistory.Size)
{
--ackCount;
func(currentAck, false);
currentAck = currentAck.IncrementAndGet();
}
// For sequence numbers contained in the history we lookup the delivery status from the history
while (ackCount > 0)
{
--ackCount;
func(currentAck, notificationData.History.IsDelivered(ackCount));
currentAck = currentAck.IncrementAndGet();
}
_outAckSeq = notificationData.AckedSeq;
}
}
private SequenceNumber UpdateInAckSeqAck(int ackCount, SequenceNumber ackedSeq)
{
if (ackCount <= _ackRecord.Count)
{
if (ackCount > 1)
{
for (var i = 0; i < ackCount - 1; i++)
{
_ackRecord.Dequeue();
}
}
var ackData = _ackRecord.Dequeue();
if (ackData.OutSeq.Equals(ackedSeq))
{
return ackData.InAckSeq;
}
}
return new SequenceNumber((ushort)(ackedSeq.Value - MaxSequenceHistoryLength));
}
}
@@ -0,0 +1,11 @@
using Prospect.Unreal.Net.Packets.Header.Sequence;
namespace Prospect.Unreal.Net.Packets.Header;
public ref struct FNotificationHeader
{
public SequenceHistory History;
public uint HistoryWordCount;
public SequenceNumber Seq;
public SequenceNumber AckedSeq;
}
@@ -0,0 +1,27 @@
using Prospect.Unreal.Net.Packets.Header.Sequence;
namespace Prospect.Unreal.Net.Packets.Header;
public class FPackedHeader
{
public const int HistoryWordCountBits = 4;
public const int SeqMask = (1 << FNetPacketNotify.SequenceNumberBits) - 1;
public const int HistoryWordCountMask = (1 << HistoryWordCountBits) - 1;
public const int AckSeqShift = HistoryWordCountBits;
public const int SeqShift = AckSeqShift + FNetPacketNotify.SequenceNumberBits;
public static SequenceNumber GetSeq(uint packed)
{
return new SequenceNumber((ushort)(packed >> SeqShift & SeqMask));
}
public static SequenceNumber GetAckedSeq(uint packed)
{
return new SequenceNumber((ushort)(packed >> AckSeqShift & SeqMask));
}
public static uint GetHistoryWordCount(uint packed)
{
return packed & HistoryWordCountMask;
}
}
@@ -0,0 +1,13 @@
namespace Prospect.Unreal.Net.Packets.Header.Sequence;
public readonly struct FSentAckData
{
public FSentAckData(SequenceNumber outSeq, SequenceNumber inAckSeq)
{
OutSeq = outSeq;
InAckSeq = inAckSeq;
}
public SequenceNumber OutSeq { get; }
public SequenceNumber InAckSeq { get; }
}
@@ -0,0 +1,46 @@
using Prospect.Unreal.Serialization;
namespace Prospect.Unreal.Net.Packets.Header.Sequence;
public readonly struct SequenceHistory
{
// Hardcoded values for FNetPacketNotify
public const uint HistorySize = FNetPacketNotify.MaxSequenceHistoryLength;
public const uint BitsPerWord = sizeof(uint) * 8;
public const uint WordCount = HistorySize / BitsPerWord;
public const uint MaxSizeInBits = WordCount * BitsPerWord;
public const uint Size = HistorySize;
private readonly uint[] _storage;
public SequenceHistory()
{
_storage = new uint[WordCount];
}
public void Read(FBitReader reader, uint numWords)
{
numWords = Math.Min(numWords, WordCount);
for (var i = 0; i < numWords; i++)
{
_storage[i] = reader.ReadUInt32();
}
}
public void Reset()
{
for (var i = 0; i < _storage.Length; i++)
{
_storage[i] = 0;
}
}
public bool IsDelivered(int index)
{
var wordIndex = (int)(index / BitsPerWord);
var wordMask = 1 << (int)(index & (BitsPerWord - 1));
return (_storage[wordIndex] & wordMask) != 0;
}
}
@@ -0,0 +1,70 @@
namespace Prospect.Unreal.Net.Packets.Header.Sequence;
public readonly struct SequenceNumber
{
// Hardcoded values for FNetPacketNotify
public const int NumBits = FNetPacketNotify.SequenceNumberBits;
public const int SeqNumberBits = NumBits;
public const ushort SeqNumberCount = 1 << NumBits;
public const ushort SeqNumberHalf = 1 << (NumBits - 1);
public const ushort SeqNumberMax = SeqNumberCount - 1;
public const ushort SeqNumberMask = SeqNumberMax;
public SequenceNumber(ushort value)
{
Value = (ushort)(value & SeqNumberMask);
}
public ushort Value { get; }
public bool Greater(SequenceNumber other)
{
return (Value != other.Value) && (((Value - other.Value) & SeqNumberMask) < SeqNumberHalf);
}
public bool GreaterEq(SequenceNumber other)
{
return ((Value - other.Value) & SeqNumberMask) < SeqNumberHalf;
}
public bool Equals(SequenceNumber other)
{
return Value == other.Value;
}
public override bool Equals(object? obj)
{
return obj is SequenceNumber other && Equals(other);
}
public override int GetHashCode()
{
return Value.GetHashCode();
}
public static int Diff(SequenceNumber a, SequenceNumber b)
{
const int shiftValue = sizeof(int) * 8 - NumBits;
var valueA = a.Value;
var valueB = b.Value;
return ((valueA - valueB) << shiftValue) >> shiftValue;
}
public SequenceNumber IncrementAndGet()
{
return new SequenceNumber((ushort)(Value + 1));
}
public readonly struct Difference
{
public Difference(int value)
{
Value = value;
}
public int Value { get; }
}
}
@@ -153,7 +153,53 @@ public class StatelessConnectHandlerComponent : HandlerComponent
public override void Incoming(FBitReader packet)
{
base.Incoming(packet);
if (_magicHeader.Length > 0)
{
// Skip magic header.
packet.Pos += _magicHeader.Length;
}
var bHandshakePacket = packet.ReadBit() && !packet.IsError();
if (bHandshakePacket)
{
var bRestartHandshake = false;
var secretId = (byte) 0;
var timestamp = 1.0d;
Span<byte> cookie = stackalloc byte[CookieByteSize];
Span<byte> origCookie = stackalloc byte[CookieByteSize];
bHandshakePacket = ParseHandshakePacket(packet, ref bRestartHandshake, ref secretId, ref timestamp, cookie, origCookie);
if (bHandshakePacket)
{
if (Handler.Mode == HandlerMode.Client)
{
throw new NotSupportedException();
}
else if (Handler.Mode == HandlerMode.Server)
{
if (_lastChallengeSuccessAddress != null)
{
// The server should not be receiving handshake packets at this stage - resend the ack in case it was lost.
// In this codepath, this component is linked to a UNetConnection, and the Last* values below, cache the handshake info.
SendChallengeAck(_lastChallengeSuccessAddress, _authorisedCookie);
}
}
}
else
{
packet.SetError();
Logger.Error("Incoming: Error reading handshake packet");
}
}
else if (packet.IsError())
{
Logger.Error("Incoming: Error reading handshake bit from packet");
}
else if (_lastChallengeSuccessAddress != null && Handler.Mode == HandlerMode.Server)
{
_lastChallengeSuccessAddress = null;
}
}
public override void Outgoing(FBitWriter packet, FOutPacketTraits traits)
@@ -181,14 +227,18 @@ public class StatelessConnectHandlerComponent : HandlerComponent
var bRestartHandshake = false;
var secretId = (byte) 0;
var timestamp = 1.0d;
var cookie = new byte[CookieByteSize];
var origCookie = new byte[CookieByteSize];
Span<byte> cookie = stackalloc byte[CookieByteSize];
Span<byte> origCookie = stackalloc byte[CookieByteSize];
bHandshakePacket = ParseHandshakePacket(packet, ref bRestartHandshake, ref secretId, ref timestamp, cookie, origCookie);
if (bHandshakePacket)
{
if (Handler.Mode == HandlerMode.Server)
if (Handler.Mode == HandlerMode.Client)
{
throw new NotSupportedException();
}
else if (Handler.Mode == HandlerMode.Server)
{
var bInitialConnect = timestamp == 0.0;
if (bInitialConnect)
@@ -206,7 +256,7 @@ public class StatelessConnectHandlerComponent : HandlerComponent
if (bValidCookieLifetime && bValidSecretIdTimestamp)
{
// Regenerate the cookie from the packet info, and see if the received cookie matches the regenerated one
var regenCookie = new byte[CookieByteSize];
Span<byte> regenCookie = stackalloc byte[CookieByteSize];
GenerateCookie(address, secretId, timestamp, regenCookie);
@@ -216,16 +266,17 @@ public class StatelessConnectHandlerComponent : HandlerComponent
{
if (bRestartHandshake)
{
Buffer.BlockCopy(origCookie, 0, _authorisedCookie, 0, _authorisedCookie.Length);
origCookie.CopyTo(_authorisedCookie);
}
else
{
var curSequence = BinaryPrimitives.ReadInt16LittleEndian(cookie);
var seqA = BinaryPrimitives.ReadInt16LittleEndian(cookie);
var seqB = BinaryPrimitives.ReadInt16LittleEndian(cookie.Slice(2));
_lastServerSequence = curSequence & (UNetConnection.MaxPacketId - 1);
_lastClientSequence = (curSequence + 1) & (UNetConnection.MaxPacketId - 1);
_lastServerSequence = seqA & (UNetConnection.MaxPacketId - 1);
_lastClientSequence = seqB & (UNetConnection.MaxPacketId - 1);
Buffer.BlockCopy(cookie, 0, _authorisedCookie, 0, _authorisedCookie.Length);
cookie.CopyTo(_authorisedCookie);
}
_bRestartedHandshake = bRestartHandshake;
@@ -262,7 +313,7 @@ public class StatelessConnectHandlerComponent : HandlerComponent
FBitReader packet,
ref bool bOutRestartHandshake,
ref byte outSecretId,
ref double outTimestamp, byte[] outCookie, byte[] outOrigCookie)
ref double outTimestamp, Span<byte> outCookie, Span<byte> outOrigCookie)
{
var bValidPacket = false;
var bitsLeft = packet.GetBitsLeft();
@@ -314,7 +365,7 @@ public class StatelessConnectHandlerComponent : HandlerComponent
var bHandshakePacket = (byte)1;
var bRestartHandshake = (byte)0;
var timestamp = _driver.GetElapsedTime();
var cookie = new byte[CookieByteSize];
Span<byte> cookie = stackalloc byte[CookieByteSize];
GenerateCookie(address, _activeSecret, timestamp, cookie);
@@ -329,7 +380,7 @@ public class StatelessConnectHandlerComponent : HandlerComponent
challengePacket.WriteDouble(timestamp);
challengePacket.Serialize(cookie, cookie.Length);
Logger.Verbose("SendConnectChallenge. Timestamp: {Timestamp}, Cookie: {Cookie}", timestamp, cookie);
Logger.Verbose("SendConnectChallenge. Timestamp: {Timestamp}, Cookie: {Cookie}", timestamp, Convert.ToHexString(cookie));
CapHandshakePacket(challengePacket);
+8
View File
@@ -0,0 +1,8 @@
using Prospect.Unreal.Core.Names;
namespace Prospect.Unreal.Net;
public class UChannel
{
public FName ChName { get; }
}
+35 -7
View File
@@ -6,20 +6,46 @@ namespace Prospect.Unreal.Net;
public class UIpConnection : UNetConnection
{
public const int IpHeaderSize = 20;
public const int UdpHeaderSize = IpHeaderSize + 8;
/// <summary>
/// Cached time of the first send socket error that will be used to compute disconnect delay.
/// </summary>
private double _socketErrorSendDelayStartTime;
/// <summary>
/// Cached time of the first recv socket error that will be used to compute disconnect delay.
/// </summary>
private double _socketErrorRecvDelayStartTime;
public UdpClient? Socket { get; private set; }
public override void InitBase(UNetDriver inDriver, UdpClient inSocket, FUrl inURL, EConnectionState inState, int inMaxPacket = 0, int inPacketOverhead = 0)
{
throw new NotImplementedException();
base.InitBase(inDriver, inSocket, inURL, inState,
(inMaxPacket == 0 || inMaxPacket > MaxPacketSize) ? MaxPacketSize : inMaxPacket,
inPacketOverhead == 0 ? UdpHeaderSize : inPacketOverhead);
Socket = inSocket;
}
public override void InitRemoteConnection(UNetDriver inDriver, UdpClient inSocket, FUrl inURL, IPEndPoint inRemoteAddr, EConnectionState inState, int inMaxPacket = 0, int inPacketOverhead = 0)
{
throw new NotImplementedException();
InitBase(inDriver, inSocket, inURL, inState,
(inMaxPacket == 0 || inMaxPacket > MaxPacketSize) ? MaxPacketSize : inMaxPacket,
inPacketOverhead == 0 ? UdpHeaderSize : inPacketOverhead);
RemoteAddr = inRemoteAddr;
Url.Host = RemoteAddr.Address;
SetClientLoginState(EClientLoginState.LoggingIn);
SetExpectedClientLoginMsgType(0); // TODO: NMT_HELLO
}
public override void InitLocalConnection(UNetDriver inDriver, UdpClient inSocket, FUrl inURL, EConnectionState inState,
int inMaxPacket = 0, int inPacketOverhead = 0)
public override void InitLocalConnection(UNetDriver inDriver, UdpClient inSocket, FUrl inURL, EConnectionState inState, int inMaxPacket = 0, int inPacketOverhead = 0)
{
throw new NotImplementedException();
throw new NotSupportedException();
}
public override void LowLevelSend(byte[] data, int countBits, FOutPacketTraits traits)
@@ -47,9 +73,11 @@ public class UIpConnection : UNetConnection
throw new NotImplementedException();
}
public override void ReceivedRawPacket(byte[] data, int count)
public override void ReceivedRawPacket(FReceivedPacketView packetView)
{
throw new NotImplementedException();
_socketErrorRecvDelayStartTime = 0;
base.ReceivedRawPacket(packetView);
}
public override float GetTimeoutValue()
+20 -7
View File
@@ -53,19 +53,27 @@ public class UIpNetDriver : UNetDriver
{
MappedClientConnections.TryGetValue(packet.Address, out connection);
}
var bIgnorePacket = false;
// If we didn't find a client connection, maybe create a new one.
if (connection == null)
{
connection = ProcessConnectionlessPacket(packet);
// TODO: bIgnorePacket
bIgnorePacket = packet.DataView.NumBytes() == 0;
}
// Send the packet to the connection for processing.
if (connection != null && !bIgnorePacket)
{
connection.ReceivedRawPacket(packet);
}
}
}
private UNetConnection? ProcessConnectionlessPacket(FReceivedPacketView packet)
{
UNetConnection? returnVal;
UNetConnection? returnVal = null;
var statelessConnect = StatelessConnectComponent;
var address = packet.Address;
var bPassedChallenge = false;
@@ -95,7 +103,7 @@ public class UIpNetDriver : UNetDriver
bIgnorePacket = false;
}
packet.DataView = new FPacketDataView(workingData, newCountBytes);
packet.DataView = new FPacketDataView(workingData, newCountBytes, ECountUnits.Bytes);
}
}
}
@@ -120,8 +128,13 @@ public class UIpNetDriver : UNetDriver
}
returnVal.InitRemoteConnection(this, Socket, World != null ? World.Url : new FUrl(), address, EConnectionState.USOCK_Open);
// if (returnVal.Handler)
if (returnVal.Handler != null)
{
returnVal.Handler.BeginHandshaking();
}
AddClientConnection(returnVal);
}
if (statelessConnect != null)
@@ -132,10 +145,10 @@ public class UIpNetDriver : UNetDriver
if (bIgnorePacket)
{
packet.DataView = new FPacketDataView(packet.DataView.GetData(), 0);
packet.DataView = new FPacketDataView(packet.DataView.GetData(), 0, ECountUnits.Bits);
}
return null;
return returnVal;
}
public override void LowLevelSend(IPEndPoint address, byte[] data, int countBits, FOutPacketTraits traits)
+661 -3
View File
@@ -1,15 +1,193 @@
using System.Net;
using System.Net.Sockets;
using Prospect.Unreal.Core;
using Prospect.Unreal.Core.Names;
using Prospect.Unreal.Exceptions;
using Prospect.Unreal.Net.Channels;
using Prospect.Unreal.Net.Packets.Bunch;
using Prospect.Unreal.Net.Packets.Header;
using Prospect.Unreal.Net.Packets.Header.Sequence;
using Prospect.Unreal.Serialization;
using Serilog;
namespace Prospect.Unreal.Net;
public abstract class UNetConnection
{
private static readonly ILogger Logger = Log.ForContext<UNetConnection>();
public const int ReliableBuffer = 256;
public const int MaxPacketId = 16384;
public const int MaxChSequence = 1024;
public const int MaxBunchHeaderBits = 256;
public const int MaxPacketSize = 1024;
public const int DefaultMaxChannelSize = 32767;
public const int MaxJitterClockTimeValue = 1023;
public const int NumBitsForJitterClockTimeInHeader = 10;
public const EEngineNetworkVersionHistory DefaultEngineNetworkProtocolVersion = EEngineNetworkVersionHistory.HISTORY_ENGINENETVERSION_LATEST;
public const uint DefaultGameNetworkProtocolVersion = 0;
private List<FBitReader>? _packetOrderCache;
private int _packetOrderCacheStartIdx;
private int _packetOrderCacheCount;
private bool _bFlushingPacketOrderCache;
private bool _bInternalAck;
private bool _bReplay;
public UNetConnection()
{
_packetOrderCache = null;
_packetOrderCacheStartIdx = 0;
_packetOrderCacheCount = 0;
_bFlushingPacketOrderCache = false;
_bInternalAck = false;
_bReplay = false;
Driver = null;
PackageMap = null;
MaxPacket = 0;
Url = new FUrl();
RemoteAddr = new IPEndPoint(IPAddress.None, 0);
State = EConnectionState.USOCK_Invalid;
Handler = null;
ClientLoginState = EClientLoginState.Invalid;
ExpectedClientLoginMsgType = 0;
PacketOverhead = 0;
InPacketId = -1;
OutPacketId = 0;
OutAckPacketId = -1;
MaxChannelSize = DefaultMaxChannelSize;
Channels = new UChannel[DefaultMaxChannelSize];
OutReliable = new int[DefaultMaxChannelSize];
InReliable = new int[DefaultMaxChannelSize];
PendingOutRec = new int[DefaultMaxChannelSize];
EngineNetworkProtocolVersion = DefaultEngineNetworkProtocolVersion;
GameNetworkProtocolVersion = DefaultGameNetworkProtocolVersion;
PacketNotify = new FNetPacketNotify();
PacketNotify.Init(
new SequenceNumber((ushort)InPacketId),
new SequenceNumber((ushort)OutPacketId));
}
/// <summary>
/// Owning net driver
/// </summary>
public UNetDriver? Driver { get; private set; }
/// <summary>
/// Package map between local and remote. (negotiates net serialization)
/// </summary>
public UPackageMapClient? PackageMap { get; private set; }
/// <summary>
/// Maximum packet size.
/// </summary>
public int MaxPacket { get; private set; }
/// <summary>
/// URL of the other side.
/// </summary>
public FUrl Url { get; }
/// <summary>
/// The remote address of this connection, typically generated from the URL.
/// </summary>
public IPEndPoint? RemoteAddr { get; protected set; }
/// <summary>
/// State this connection is in.
/// </summary>
public EConnectionState State { get; private set; }
/// <summary>
/// PacketHandler, for managing layered handler components, which modify packets as they are sent/received
/// </summary>
public PacketHandler? Handler { get; private set; }
public EClientLoginState ClientLoginState { get; private set; }
/// <summary>
/// Used to determine what the next expected control channel msg type should be from a connecting client
/// </summary>
public byte ExpectedClientLoginMsgType { get; private set; }
/// <summary>
/// Reference to the PacketHandler component, for managing stateless connection handshakes
/// </summary>
public StatelessConnectHandlerComponent? StatelessConnectComponent { get; private set; }
/// <summary>
/// Bytes overhead per packet sent.
/// </summary>
public int PacketOverhead { get; private set; }
/// <summary>
/// Full incoming packet index.
/// </summary>
public int InPacketId { get; private set; }
/// <summary>
/// Most recently sent packet.
/// </summary>
public int OutPacketId { get; private set; }
/// <summary>
/// Most recently acked outgoing packet.
/// </summary>
public int OutAckPacketId { get; private set; }
public bool LastHasServerFrameTime { get; private set; }
/// <summary>
/// Full PacketId of last sent packet that we have received notification for (i.e. we know if it was delivered or not).
/// Related to OutAckPacketId which is tha last successfully delivered PacketId.
/// </summary>
public int LastNotifiedPacketId { get; private set; }
public int MaxChannelSize { get; }
public UChannel?[] Channels { get; }
public int[] OutReliable { get; }
public int[] InReliable { get; }
public int[] PendingOutRec { get; }
public int InitOutReliable { get; private set; }
public int InitInReliable { get; private set; }
public EEngineNetworkVersionHistory EngineNetworkProtocolVersion { get; private set; }
public uint GameNetworkProtocolVersion { get; private set; }
public FNetPacketNotify PacketNotify { get; }
public virtual void InitBase(UNetDriver inDriver, UdpClient inSocket, FUrl inURL, EConnectionState inState, int inMaxPacket = 0, int inPacketOverhead = 0)
{
Driver = inDriver;
// TODO: ConnectionId
State = inState;
Url.Protocol = inURL.Protocol;
Url.Host = inURL.Host;
Url.Port = inURL.Port;
Url.Map = inURL.Map;
Url.RedirectUrl = inURL.RedirectUrl;
Url.Options = inURL.Options;
Url.Portal = inURL.Portal;
MaxPacket = inMaxPacket;
PacketOverhead = inPacketOverhead;
InitHandler();
PackageMap = new UPackageMapClient();
PackageMap.Initialize(this, Driver.GuidCache);
}
public abstract void InitBase(UNetDriver inDriver, UdpClient inSocket, FUrl inURL, EConnectionState inState, int inMaxPacket = 0, int inPacketOverhead = 0);
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 abstract void LowLevelSend(byte[] data, int countBits, FOutPacketTraits traits);
@@ -17,11 +195,491 @@ public abstract class UNetConnection
public abstract string LowLevelDescribe();
public abstract void Tick(float deltaSeconds);
public abstract void CleanUp();
public abstract void ReceivedRawPacket(byte[] data, int count);
public virtual void ReceivedRawPacket(FReceivedPacketView packetView)
{
if (Handler != null)
{
var result = Handler.Incoming(packetView);
if (result)
{
if (packetView.DataView.NumBytes() == 0)
{
return;
}
}
else
{
// TODO: Close connection, malformed packet.
Logger.Fatal("Connection should be closed here");
return;
}
}
// Handle an incoming raw packet from the driver.
if (packetView.DataView.NumBytes() > 0)
{
var data = packetView.DataView.GetData();
var count = packetView.DataView.NumBytes();
var lastByte = data[count - 1];
if (lastByte != 0)
{
var bitSize = (count * 8) - 1;
// Bit streaming, starts at the Least Significant Bit, and ends at the MSB.
while ((lastByte & 0x80) == 0)
{
lastByte *= 2;
bitSize--;
}
var reader = new FBitReader(data, bitSize);
reader.SetEngineNetVer(EngineNetworkProtocolVersion);
reader.SetGameNetVer(GameNetworkProtocolVersion);
if (Handler != null)
{
Handler.IncomingHigh(reader);
}
if (reader.GetBitsLeft() > 0)
{
ReceivedPacket(reader);
// TODO: Flush out of order cache
}
}
}
}
private void ReceivedPacket(FBitReader reader, bool bIsReinjectedPacket = false)
{
if (reader.IsError())
{
Logger.Error("Packet too small");
return;
}
var resetReaderMark = new FBitReaderMark(reader);
if (_bInternalAck)
{
++InPacketId;
}
else
{
// Read packet header.
var header = new FNotificationHeader();
if (!PacketNotify.ReadHeader(ref header, reader))
{
// TODO: Close connection, malformed packet.
Logger.Fatal("Failed to read PacketHeader");
return;
}
var bHasPacketInfoPayload = true;
if (reader.EngineNetVer() > EEngineNetworkVersionHistory.HISTORY_JITTER_IN_HEADER)
{
bHasPacketInfoPayload = reader.ReadBit();
if (bHasPacketInfoPayload)
{
var bitsReadPreJitterClock = reader.GetPosBits();
var packetJitterClockTimeMs = reader.ReadInt(MaxJitterClockTimeValue + 1);
if (reader.GetPosBits() - bitsReadPreJitterClock != NumBitsForJitterClockTimeInHeader)
{
throw new UnrealNetException("JitterClockTime did not read the expected amount of bits");
}
if (!bIsReinjectedPacket)
{
ProcessJitter(packetJitterClockTimeMs);
}
}
}
var packetSequenceDelta = PacketNotify.GetSequenceDelta(header);
if (packetSequenceDelta > 0)
{
var bPacketOrderCacheActive = !_bFlushingPacketOrderCache && _packetOrderCache != null;
var bCheckForMissingSequence = bPacketOrderCacheActive && _packetOrderCacheCount == 0;
var bFillingPacketOrderCache = bPacketOrderCacheActive && _packetOrderCacheCount > 0;
const int maxMissingPackets = 0; // CVarNetPacketOrderMaxMissingPackets
var missingPacketCount = packetSequenceDelta - 1;
// Cache the packet if we are already caching, and begin caching if we just encountered a missing sequence, within range
if (bFillingPacketOrderCache || (bCheckForMissingSequence && missingPacketCount > 0 && missingPacketCount <= maxMissingPackets))
{
throw new NotImplementedException();
}
if (missingPacketCount > 10)
{
Logger.Verbose("High single frame packet loss, PacketsLost: {Amount}", missingPacketCount);
}
// TODO: Increment things
}
else
{
// TODO: Increment things
// TODO: PacketOrderCache
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");
}
});
// 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");
return;
}
}
var bIgnoreRPCS = Driver!.ShouldIgnoreRPCs();
var bSickAcp = false;
// Track channels that were rejected while processing this packet - used to avoid sending multiple close-channel bunches,
// which would cause a disconnect serverside
var rejectedChans = new List<int>();
// Disassemble and dispatch all bunches in the packet.
while (!reader.AtEnd() && State != EConnectionState.USOCK_Closed)
{
if (_bInternalAck && EngineNetworkProtocolVersion < EEngineNetworkVersionHistory.HISTORY_ACKS_INCLUDED_IN_HEADER)
{
_ = reader.ReadBit();
}
// Parse the bunch.
var startPos = reader.GetPosBits();
// Process Received data
{
// Parse the incoming data.
var bunch = new FInBunch(this);
var incomingStartPos = reader.GetPosBits();
var bControl = reader.ReadBit();
bunch.PacketId = InPacketId;
bunch.bOpen = bControl && reader.ReadBit();
bunch.bClose = bControl && reader.ReadBit();
if (bunch.EngineNetVer() < EEngineNetworkVersionHistory.HISTORY_CHANNEL_CLOSE_REASON)
{
bunch.bDormant = bunch.bClose && reader.ReadBit();
bunch.CloseReason = bunch.bDormant
? EChannelCloseReason.Dormancy
: EChannelCloseReason.Destroyed;
}
else
{
bunch.CloseReason = bunch.bClose
? (EChannelCloseReason)reader.ReadInt((uint)EChannelCloseReason.MAX)
: EChannelCloseReason.Destroyed;
bunch.bDormant = bunch.CloseReason == EChannelCloseReason.Dormancy;
}
bunch.bIsReplicationPaused = reader.ReadBit();
bunch.bReliable = reader.ReadBit();
if (bunch.EngineNetVer() < EEngineNetworkVersionHistory.HISTORY_MAX_ACTOR_CHANNELS_CUSTOMIZATION)
{
const int oldMaxActorChannels = 10240;
bunch.ChIndex = (int)reader.ReadInt(oldMaxActorChannels);
}
else
{
bunch.ChIndex = (int)reader.ReadUInt32Packed();
if (bunch.ChIndex >= MaxChannelSize)
{
throw new Exception("Bunch channel index exceeds channel limit");
}
}
// if flag is set, remap channel index values, we're fast forwarding a replay checkpoint
// and there should be no bunches for existing channels
if (_bInternalAck /* && bAllowExistingChannelIndex */ && (bunch.EngineNetVer() >= EEngineNetworkVersionHistory.HISTORY_REPLAY_DORMANCY))
{
throw new NotSupportedException("Replay code");
}
bunch.bHasPackageMapExports = reader.ReadBit();
bunch.bHasMustBeMappedGUIDs = reader.ReadBit();
bunch.bPartial = reader.ReadBit();
if (bunch.bReliable)
{
if (_bInternalAck)
{
// We can derive the sequence for 100% reliable connections.
bunch.ChSequence = InReliable[bunch.ChIndex] + 1;
}
else
{
bunch.ChSequence = MakeRelative((int)reader.ReadInt(MaxChSequence), InReliable[bunch.ChIndex], MaxChSequence);
}
}
else if (bunch.bPartial)
{
// If this is an unreliable partial bunch, we simply use packet sequence since we already have it
bunch.ChSequence = InPacketId;
}
else
{
bunch.ChSequence = 0;
}
bunch.bPartialInitial = bunch.bPartial && reader.ReadBit();
bunch.bPartialFinal = bunch.bPartial && reader.ReadBit();
if (bunch.EngineNetVer() < EEngineNetworkVersionHistory.HISTORY_CHANNEL_NAMES)
{
bunch.ChType = (bunch.bReliable || bunch.bOpen) ? (int) reader.ReadInt(EChannelType.CHTYPE_MAX) : EChannelType.CHTYPE_None;
switch (bunch.ChType)
{
case EChannelType.CHTYPE_Control:
bunch.ChName = UnrealNames.FNames[UnrealNameKey.Control];
break;
case EChannelType.CHTYPE_Voice:
bunch.ChName = UnrealNames.FNames[UnrealNameKey.Voice];
break;
case EChannelType.CHTYPE_Actor:
bunch.ChName = UnrealNames.FNames[UnrealNameKey.Actor];
break;
}
}
else
{
if (bunch.bReliable || bunch.bOpen)
{
if (!UPackageMap.StaticSerializeName(reader, out var chName) || reader.IsError())
{
// TODO: Close connection
Logger.Fatal("Channel name serialization failed");
return;
}
bunch.ChName = chName;
switch ((UnrealNameKey) bunch.ChName.Number)
{
case UnrealNameKey.Control:
bunch.ChType = EChannelType.CHTYPE_Control;
break;
case UnrealNameKey.Voice:
bunch.ChType = EChannelType.CHTYPE_Voice;
break;
case UnrealNameKey.Actor:
bunch.ChType = EChannelType.CHTYPE_Actor;
break;
}
}
else
{
bunch.ChType = EChannelType.CHTYPE_None;
bunch.ChName = UnrealNames.FNames[UnrealNameKey.None];
}
}
var channel = Channels[bunch.ChIndex];
// If there's an existing channel and the bunch specified it's channel type, make sure they match.
if (channel != null &&
(bunch.ChName.Number != (int)UnrealNameKey.None) &&
(bunch.ChName.Number != channel.ChName.Number))
{
Logger.Error("Existing channel at index {ChIndex} with type \"{ChName}\" differs from the incoming bunch's expected channel type, \"{BunchChName}\"",
bunch.ChIndex, channel.ChName.Str, bunch.ChName.Str);
// TODO: Close();
return;
}
}
}
throw new NotImplementedException();
}
private bool ReadPacketInfo(FBitReader reader, bool bHasPacketInfoPayload)
{
if (!bHasPacketInfoPayload)
{
var bCanContinueReading = reader.IsError() == false;
return bCanContinueReading;
}
var bHasServerFrameTime = reader.ReadBit();
var serverFrameTime = 0.0d;
if (!Driver!.IsServer())
{
throw new NotImplementedException("Client code");
}
else
{
LastHasServerFrameTime = bHasServerFrameTime;
}
if (reader.EngineNetVer() < EEngineNetworkVersionHistory.HISTORY_JITTER_IN_HEADER)
{
// RemoteInKBytesPerSecondByte
reader.ReadByte();
}
if (reader.IsError())
{
return false;
}
// TODO: Update ping, lag measuring
return true;
}
private void ProcessJitter(uint packetJitterClockTimeMs)
{
if (packetJitterClockTimeMs >= MaxJitterClockTimeValue)
{
return;
}
Logger.Verbose("Jitter calculations missing");
}
public abstract float GetTimeoutValue();
public void InitSequence(int clientSequence, int serverSequence)
public void InitSequence(int incomingSequence, int outgoingSequence)
{
if (InPacketId == -1)
{
// Initialize the base UNetConnection packet sequence (not very useful/effective at preventing attacks)
InPacketId = incomingSequence - 1;
OutPacketId = outgoingSequence;
OutAckPacketId = outgoingSequence - 1;
LastNotifiedPacketId = OutAckPacketId;
// Initialize the reliable packet sequence (more useful/effective at preventing attacks)
InitInReliable = incomingSequence & (MaxChSequence - 1);
InitOutReliable = outgoingSequence & (MaxChSequence - 1);
for (var i = 0; i < InReliable.Length; i++)
{
InReliable[i] = InitInReliable;
}
for (var i = 0; i < OutReliable.Length; i++)
{
OutReliable[i] = InitOutReliable;
}
PacketNotify.Init(
new SequenceNumber((ushort)InPacketId),
new SequenceNumber((ushort)OutPacketId));
Logger.Verbose("InitSequence: IncomingSequence: {SeqA}, OutgoingSequence: {SeqB}", incomingSequence, outgoingSequence);
Logger.Verbose("InitSequence: InitInReliable: {In}, InitOutReliable: {Out}", InitInReliable, InitOutReliable);
}
}
private void InitHandler()
{
if (Handler != null)
{
return;
}
if (Driver == null)
{
throw new UnrealNetException("Driver was null");
}
Handler = new PacketHandler();
Handler.Initialize(false);
StatelessConnectComponent = (StatelessConnectHandlerComponent) Handler.AddHandler<StatelessConnectHandlerComponent>();
StatelessConnectComponent.SetDriver(Driver);
Handler.InitializeComponents();
}
public void CreateChannelByName(string chName, EChannelCreateFlags createFlags, int channelIndex)
{
// TODO: Implement
Logger.Verbose("Creating channel {Name} with index {Index}", chName, channelIndex);
}
private protected void SetClientLoginState(EClientLoginState newState)
{
if (ClientLoginState == newState)
{
Logger.Verbose("SetClientLoginState: State same: {Old}", newState);
return;
}
Logger.Verbose("SetClientLoginState: State changing from {Old} to {New}", ClientLoginState, newState);
ClientLoginState = newState;
}
private protected void SetExpectedClientLoginMsgType(byte newType)
{
if (ExpectedClientLoginMsgType == newType)
{
Logger.Verbose("SetExpectedClientLoginMsgType: Type same: [{Old}]", newType);
return;
}
Logger.Verbose("SetExpectedClientLoginMsgType: Type changing from [{Old}] to [{New}]", ExpectedClientLoginMsgType, newType);
ExpectedClientLoginMsgType = newType;
}
private static int BestSignedDifference(int value, int reference, int max)
{
return ((value - reference + max / 2) & (max - 1)) - max / 2;
}
private static int MakeRelative(int value, int reference, int max)
{
return reference + BestSignedDifference(value, reference, max);
}
}
+60 -1
View File
@@ -1,5 +1,6 @@
using System.Collections.Concurrent;
using System.Net;
using Prospect.Unreal.Net.Channels;
using Prospect.Unreal.Runtime;
namespace Prospect.Unreal.Net;
@@ -10,6 +11,15 @@ public abstract class UNetDriver : IAsyncDisposable
protected UNetDriver()
{
GuidCache = new FNetGUIDCache(this);
// TODO: Load from Engine ini
ChannelDefinitions = new List<FChannelDefinition>
{
new FChannelDefinition("Control", typeof(string), 0, true, false, true, false, true),
new FChannelDefinition("Voice", typeof(string), 1, true, true, true, true, true),
new FChannelDefinition("Actor", typeof(string), -1, false, true, false, false, false)
};
ClientConnections = new List<UNetConnection>();
MappedClientConnections = new ConcurrentDictionary<IPEndPoint, UNetConnection>();
}
@@ -18,6 +28,18 @@ public abstract class UNetDriver : IAsyncDisposable
/// </summary>
public UWorld? World { get; private set; }
public FNetGUIDCache GuidCache { get; }
/// <summary>
/// Used to specify available channel types and their associated UClass
/// </summary>
public List<FChannelDefinition> ChannelDefinitions { get; }
/// <summary>
/// Array of connections to clients (this net driver is a host) - unsorted, and ordering changes depending on actor replication
/// </summary>
public List<UNetConnection> ClientConnections { get; }
/// <summary>
/// Map of <see cref="IPEndPoint"/> to <see cref="UNetConnection"/>.
/// </summary>
@@ -75,6 +97,16 @@ public abstract class UNetDriver : IAsyncDisposable
// TODO: AddInitialObjects?
}
}
public bool IsServer()
{
return true;
}
public virtual bool ShouldIgnoreRPCs()
{
return false;
}
public double GetElapsedTime()
{
@@ -85,7 +117,34 @@ public abstract class UNetDriver : IAsyncDisposable
{
_elapsedTime = 0.0;
}
private protected void AddClientConnection(UNetConnection newConnection)
{
ClientConnections.Add(newConnection);
if (newConnection.RemoteAddr != null)
{
MappedClientConnections[newConnection.RemoteAddr] = newConnection;
// TODO: RecentlyDisconnectedClients ?
}
CreateInitialServerChannels(newConnection);
// TODO: NetworkObjectList > HandleConnectionAdded
}
private void CreateInitialServerChannels(UNetConnection clientConnection)
{
foreach (var channelDef in ChannelDefinitions)
{
if (channelDef.InitialServer)
{
clientConnection.CreateChannelByName(channelDef.Name, EChannelCreateFlags.OpenedLocally, channelDef.StaticChannelIndex);
}
}
}
public virtual ValueTask DisposeAsync()
{
return ValueTask.CompletedTask;
+56
View File
@@ -0,0 +1,56 @@
using System.Diagnostics.CodeAnalysis;
using Prospect.Unreal.Core;
using Prospect.Unreal.Core.Names;
using Prospect.Unreal.Serialization;
namespace Prospect.Unreal.Net;
public class UPackageMap
{
public static unsafe bool StaticSerializeName(FBitReader ar, [MaybeNullWhen(false)] out FName name)
{
if (!ar.IsLoading())
{
throw new NotImplementedException();
}
name = null;
bool bHardcoded;
ar.SerializeBits(&bHardcoded, 1);
if (bHardcoded)
{
// replicated by hardcoded index
uint nameIndex;
if (ar.EngineNetVer() < EEngineNetworkVersionHistory.HISTORY_CHANNEL_NAMES)
{
ar.SerializeInt(&nameIndex, UnrealNames.MaxNetworkedHardcodedName);
}
else
{
ar.SerializeIntPacked(&nameIndex);
}
if (nameIndex < UnrealNames.MaxHardcodedNameIndex)
{
// hardcoded names never have a Number
name = UnrealNames.FNames[(UnrealNameKey) nameIndex];
}
else
{
ar.SetError();
}
}
else
{
// replicated by string
var inString = FString.Deserialize(ar);
var inNumber = ar.ReadInt32();
name = new FName(inString, inNumber);
}
return true;
}
}
@@ -0,0 +1,9 @@
namespace Prospect.Unreal.Net;
public class UPackageMapClient
{
public void Initialize(UNetConnection connection, FNetGUIDCache guidCache)
{
// TODO: Implement
}
}
@@ -0,0 +1,21 @@
namespace Prospect.Unreal.Serialization;
public class FBitReaderMark
{
private long _pos;
public FBitReaderMark(FBitReader reader)
{
_pos = reader.Pos;
}
public long GetPos()
{
return _pos;
}
public void Pop(FBitReader reader)
{
reader.Pos = _pos;
}
}
@@ -0,0 +1,18 @@
using Prospect.Unreal.Net;
namespace Prospect.Unreal.Serialization;
public class FNetBitReader : FBitReader
{
public FNetBitReader(byte[] src) : base(src)
{
throw new NotSupportedException();
}
public FNetBitReader(UPackageMapClient? inPackageMap, byte[]? src, int num) : base(src, num)
{
PackageMap = inPackageMap;
}
public UPackageMapClient? PackageMap { get; }
}