Handle NMT Login packet
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
using Prospect.Unreal.Core;
|
using Prospect.Unreal.Core;
|
||||||
|
using Prospect.Unreal.Runtime;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Prospect.Server.Game;
|
namespace Prospect.Server.Game;
|
||||||
@@ -33,6 +34,8 @@ internal static class Program
|
|||||||
|
|
||||||
await using (var world = new ProspectWorld(worldUrl))
|
await using (var world = new ProspectWorld(worldUrl))
|
||||||
{
|
{
|
||||||
|
world.SetGameInstance(new UGameInstance());
|
||||||
|
world.SetGameMode(worldUrl);
|
||||||
world.Listen();
|
world.Listen();
|
||||||
|
|
||||||
while (await Tick.WaitForNextTickAsync())
|
while (await Tick.WaitForNextTickAsync())
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ namespace Prospect.Unreal.Generator
|
|||||||
{ "int", new ParamDef("bunch.ReadInt32()", "bunch.WriteInt32({0})") },
|
{ "int", new ParamDef("bunch.ReadInt32()", "bunch.WriteInt32({0})") },
|
||||||
{ "uint", new ParamDef("bunch.ReadUInt32()", "bunch.WriteUInt32({0})") },
|
{ "uint", new ParamDef("bunch.ReadUInt32()", "bunch.WriteUInt32({0})") },
|
||||||
{ "FString", new ParamDef("bunch.ReadString()", "bunch.WriteString({0})") },
|
{ "FString", new ParamDef("bunch.ReadString()", "bunch.WriteString({0})") },
|
||||||
|
{ "FUniqueNetIdRepl", new ParamDef("FUniqueNetIdRepl.Read(bunch)", "FUniqueNetIdRepl.Write(bunch, {0})") }
|
||||||
};
|
};
|
||||||
|
|
||||||
public static string SendParams(List<string> args)
|
public static string SendParams(List<string> args)
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using NUnit.Framework;
|
||||||
|
using Prospect.Unreal.Core;
|
||||||
|
using Prospect.Unreal.Serialization;
|
||||||
|
|
||||||
|
namespace Prospect.Unreal.Tests.Core;
|
||||||
|
|
||||||
|
public class FStringTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
[TestCase("")]
|
||||||
|
[TestCase("TestString")]
|
||||||
|
[TestCase("/Game/ThirdPersonCPP/Maps/ThirdPersonExampleMap")]
|
||||||
|
[TestCase("/Script/ThirdPersonMP.ThirdPersonMPGameMode")]
|
||||||
|
public void TestWriteRead(string testValue)
|
||||||
|
{
|
||||||
|
// Write a string.
|
||||||
|
using var writer = new FNetBitWriter(4096);
|
||||||
|
|
||||||
|
FString.Serialize(writer, testValue);
|
||||||
|
|
||||||
|
// Create a reader.
|
||||||
|
var reader = new FNetBitReader(null, writer.GetData(), (int)writer.GetNumBits());
|
||||||
|
var read = FString.Deserialize(reader);
|
||||||
|
|
||||||
|
Assert.AreEqual(testValue, read);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using Prospect.Unreal.Net;
|
using Prospect.Unreal.Serialization;
|
||||||
using Prospect.Unreal.Serialization;
|
|
||||||
|
|
||||||
namespace Prospect.Unreal.Core;
|
namespace Prospect.Unreal.Core;
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,12 @@ public static class FString
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var num = value.Length + 1;
|
var num = value.Length;
|
||||||
|
if (num != 0)
|
||||||
|
{
|
||||||
|
// Add null terminator.
|
||||||
|
num += 1;
|
||||||
|
|
||||||
var valueBytes = num > 128 ? new byte[num] : stackalloc byte[num];
|
var valueBytes = num > 128 ? new byte[num] : stackalloc byte[num];
|
||||||
|
|
||||||
Encoding.ASCII.GetBytes(value, valueBytes);
|
Encoding.ASCII.GetBytes(value, valueBytes);
|
||||||
@@ -49,6 +54,11 @@ public static class FString
|
|||||||
archive.WriteInt32(num);
|
archive.WriteInt32(num);
|
||||||
archive.Serialize(valueBytes, num);
|
archive.Serialize(valueBytes, num);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
archive.WriteInt32(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string Deserialize(FArchive archive)
|
public static string Deserialize(FArchive archive)
|
||||||
|
|||||||
@@ -1,14 +1,75 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace Prospect.Unreal.Core;
|
namespace Prospect.Unreal.Core;
|
||||||
|
|
||||||
public class FUrl
|
public class FUrl
|
||||||
{
|
{
|
||||||
public string Protocol { get; set; } = "unreal";
|
private const string DefaultProtocol = "unreal";
|
||||||
public IPAddress Host { get; set; } = IPAddress.Any;
|
private static readonly IPAddress DefaultHost = IPAddress.Any;
|
||||||
public int Port { get; set; } = 7777;
|
private const int DefaultPort = 7777;
|
||||||
public string Map { get; set; } = "GearStart";
|
|
||||||
|
public string Protocol { get; set; } = DefaultProtocol;
|
||||||
|
public IPAddress Host { get; set; } = DefaultHost;
|
||||||
|
public int Port { get; set; } = DefaultPort;
|
||||||
|
public string Map { get; set; } = "GearStart"; // TODO: UGameMapsSettings::GetGameDefaultMap()
|
||||||
public string RedirectUrl { get; set; } = string.Empty;
|
public string RedirectUrl { get; set; } = string.Empty;
|
||||||
public List<string> Options { get; set; } = new List<string>();
|
public List<string> Options { get; set; } = new List<string>();
|
||||||
public string Portal { get; set; } = string.Empty;
|
public string Portal { get; set; } = string.Empty;
|
||||||
|
public bool Valid { get; set; } = true;
|
||||||
|
|
||||||
|
public string ToString(bool fullyQualified)
|
||||||
|
{
|
||||||
|
var result = new StringBuilder();
|
||||||
|
|
||||||
|
// Emit protocol.
|
||||||
|
if ((Protocol != DefaultProtocol) || fullyQualified)
|
||||||
|
{
|
||||||
|
result.Append(Protocol);
|
||||||
|
result.Append(':');
|
||||||
|
|
||||||
|
if (!Equals(Host, DefaultHost))
|
||||||
|
{
|
||||||
|
result.Append("//");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit host and port
|
||||||
|
if (!Equals(Host, DefaultHost) || (Port != DefaultPort))
|
||||||
|
{
|
||||||
|
result.Append(Port);
|
||||||
|
|
||||||
|
if (!Map.StartsWith("/") && !Map.StartsWith("\\"))
|
||||||
|
{
|
||||||
|
result.Append('/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit map.
|
||||||
|
if (!string.IsNullOrEmpty(Map))
|
||||||
|
{
|
||||||
|
result.Append(Map);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit options.
|
||||||
|
foreach (var option in Options)
|
||||||
|
{
|
||||||
|
result.Append('?');
|
||||||
|
result.Append(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit portal.
|
||||||
|
if (!string.IsNullOrEmpty(Portal))
|
||||||
|
{
|
||||||
|
result.Append('#');
|
||||||
|
result.Append(Portal);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return ToString(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
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;
|
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
|
public class FNetPacketNotify
|
||||||
{
|
{
|
||||||
@@ -133,14 +145,14 @@ public class FNetPacketNotify
|
|||||||
return SequenceNumber.Diff(notificationData.Seq, _inSeq);
|
return SequenceNumber.Diff(notificationData.Seq, _inSeq);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Update(FNotificationHeader notificationData, HandlePacketNotification func)
|
public int Update(FNotificationHeader notificationData, PacketNotifyUpdateContext context)
|
||||||
{
|
{
|
||||||
var inSeqDelta = GetSequenceDelta(notificationData);
|
var inSeqDelta = GetSequenceDelta(notificationData);
|
||||||
if (inSeqDelta > 0)
|
if (inSeqDelta > 0)
|
||||||
{
|
{
|
||||||
Logger.Verbose("Update - Seq {Seq}, InSeq {InSeq}", notificationData.Seq.Value, _inSeq.Value);
|
Logger.Verbose("Update - Seq {Seq}, InSeq {InSeq}", notificationData.Seq.Value, _inSeq.Value);
|
||||||
|
|
||||||
ProcessReceivedAcks(notificationData, func);
|
ProcessReceivedAcks(notificationData, context);
|
||||||
|
|
||||||
_inSeq = notificationData.Seq;
|
_inSeq = notificationData.Seq;
|
||||||
|
|
||||||
@@ -150,7 +162,7 @@ public class FNetPacketNotify
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProcessReceivedAcks(FNotificationHeader notificationData, HandlePacketNotification func)
|
private void ProcessReceivedAcks(FNotificationHeader notificationData, PacketNotifyUpdateContext context)
|
||||||
{
|
{
|
||||||
if (notificationData.AckedSeq.Greater(_outAckSeq))
|
if (notificationData.AckedSeq.Greater(_outAckSeq))
|
||||||
{
|
{
|
||||||
@@ -173,7 +185,7 @@ public class FNetPacketNotify
|
|||||||
while (ackCount > SequenceHistory.Size)
|
while (ackCount > SequenceHistory.Size)
|
||||||
{
|
{
|
||||||
--ackCount;
|
--ackCount;
|
||||||
func(currentAck, false);
|
context.Func(context, currentAck, false);
|
||||||
currentAck = currentAck.IncrementAndGet();
|
currentAck = currentAck.IncrementAndGet();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,7 +193,7 @@ public class FNetPacketNotify
|
|||||||
while (ackCount > 0)
|
while (ackCount > 0)
|
||||||
{
|
{
|
||||||
--ackCount;
|
--ackCount;
|
||||||
func(currentAck, notificationData.History.IsDelivered(ackCount));
|
context.Func(context, currentAck, notificationData.History.IsDelivered(ackCount));
|
||||||
currentAck = currentAck.IncrementAndGet();
|
currentAck = currentAck.IncrementAndGet();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,7 +243,7 @@ public class FNetPacketNotify
|
|||||||
{
|
{
|
||||||
while (ackedSeq.Greater(_inAckSeq))
|
while (ackedSeq.Greater(_inAckSeq))
|
||||||
{
|
{
|
||||||
_inAckSeq.IncrementAndGet();
|
_inAckSeq = _inAckSeq.IncrementAndGet();
|
||||||
|
|
||||||
var bReportAcked = _inAckSeq.Equals(ackedSeq) ? isAck : false;
|
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.
|
// 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));
|
_ackRecord.Enqueue(new FSentAckData(_outSeq, _writtenInAckSeq));
|
||||||
_writtenHistoryWordCount = 0;
|
_writtenHistoryWordCount = 0;
|
||||||
|
_outSeq = _outSeq.IncrementAndGet();
|
||||||
|
|
||||||
return _outSeq.IncrementAndGet();
|
return _outSeq;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Net;
|
||||||
using System.Net;
|
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using Prospect.Unreal.Core;
|
using Prospect.Unreal.Core;
|
||||||
using Prospect.Unreal.Core.Names;
|
using Prospect.Unreal.Core.Names;
|
||||||
@@ -38,7 +37,19 @@ public abstract class UNetConnection : UPlayer
|
|||||||
public const EEngineNetworkVersionHistory DefaultEngineNetworkProtocolVersion = EEngineNetworkVersionHistory.HISTORY_ENGINENETVERSION_LATEST;
|
public const EEngineNetworkVersionHistory DefaultEngineNetworkProtocolVersion = EEngineNetworkVersionHistory.HISTORY_ENGINENETVERSION_LATEST;
|
||||||
public const uint DefaultGameNetworkProtocolVersion = 0;
|
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;
|
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 List<FBitReader>? _packetOrderCache;
|
||||||
private int _packetOrderCacheStartIdx;
|
private int _packetOrderCacheStartIdx;
|
||||||
private int _packetOrderCacheCount;
|
private int _packetOrderCacheCount;
|
||||||
@@ -48,6 +59,7 @@ public abstract class UNetConnection : UPlayer
|
|||||||
private bool _bReplay;
|
private bool _bReplay;
|
||||||
|
|
||||||
private double _statUpdateTime;
|
private double _statUpdateTime;
|
||||||
|
|
||||||
private double _lastReceiveTime;
|
private double _lastReceiveTime;
|
||||||
private double _lastReceiveRealTime;
|
private double _lastReceiveRealTime;
|
||||||
private double _lastGoodPacketRealtime;
|
private double _lastGoodPacketRealtime;
|
||||||
@@ -73,16 +85,21 @@ public abstract class UNetConnection : UPlayer
|
|||||||
private bool _bFlushedNetThisFrame;
|
private bool _bFlushedNetThisFrame;
|
||||||
private bool _bAutoFlush;
|
private bool _bAutoFlush;
|
||||||
|
|
||||||
|
private readonly HandlePacketNotification _packetNotifyUpdateDelegate;
|
||||||
|
|
||||||
public UNetConnection()
|
public UNetConnection()
|
||||||
{
|
{
|
||||||
_channelsToTick = new HashSet<UChannel>();
|
_channelsToTick = new HashSet<UChannel>();
|
||||||
|
_playerOnlinePlatformName = UnrealNames.FNames[UnrealNameKey.None];
|
||||||
_packetOrderCache = null;
|
_packetOrderCache = null;
|
||||||
_packetOrderCacheStartIdx = 0;
|
_packetOrderCacheStartIdx = 0;
|
||||||
_packetOrderCacheCount = 0;
|
_packetOrderCacheCount = 0;
|
||||||
_bFlushingPacketOrderCache = false;
|
_bFlushingPacketOrderCache = false;
|
||||||
_bInternalAck = false;
|
_bInternalAck = false;
|
||||||
_bReplay = false;
|
_bReplay = false;
|
||||||
|
_packetNotifyUpdateDelegate = PacketNotifyUpdate;
|
||||||
|
|
||||||
|
Children = new List<UChildConnection>();
|
||||||
Driver = null;
|
Driver = null;
|
||||||
PackageMap = null;
|
PackageMap = null;
|
||||||
OpenChannels = new List<UChannel>();
|
OpenChannels = new List<UChannel>();
|
||||||
@@ -116,6 +133,11 @@ public abstract class UNetConnection : UPlayer
|
|||||||
new SequenceNumber((ushort)OutPacketId));
|
new SequenceNumber((ushort)OutPacketId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Child connections for secondary viewports
|
||||||
|
/// </summary>
|
||||||
|
public List<UChildConnection> Children { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Owning net driver
|
/// Owning net driver
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -198,6 +220,11 @@ public abstract class UNetConnection : UPlayer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public StatelessConnectHandlerComponent? StatelessConnectComponent { get; private set; }
|
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>
|
/// <summary>
|
||||||
/// Bytes overhead per packet sent.
|
/// Bytes overhead per packet sent.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -208,6 +235,21 @@ public abstract class UNetConnection : UPlayer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string Challenge { get; private set; }
|
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>
|
/// <summary>
|
||||||
/// Queued up bits waiting to send
|
/// Queued up bits waiting to send
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -321,8 +363,16 @@ public abstract class UNetConnection : UPlayer
|
|||||||
PackageMap = packageMapClient;
|
PackageMap = packageMapClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void InitRemoteConnection(UNetDriver inDriver, UdpClient inSocket, FUrl inURL, IPEndPoint inRemoteAddr, 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)
|
||||||
public abstract void InitLocalConnection(UNetDriver inDriver, UdpClient inSocket, FUrl inURL, 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 void LowLevelSend(byte[] data, int countBits, FOutPacketTraits traits);
|
||||||
public abstract string LowLevelGetRemoteAddress(bool bAppendPort = false);
|
public abstract string LowLevelGetRemoteAddress(bool bAppendPort = false);
|
||||||
public abstract string LowLevelDescribe();
|
public abstract string LowLevelDescribe();
|
||||||
@@ -463,44 +513,25 @@ public abstract class UNetConnection : UPlayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Increment things
|
// TODO: Increment things
|
||||||
|
InPacketId += packetSequenceDelta;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// TODO: Increment things
|
// TODO: Increment things
|
||||||
// TODO: PacketOrderCache
|
// TODO: PacketOrderCache
|
||||||
|
Logger.Warning("Received out of order packet");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update incoming sequence data and deliver packet notifications
|
// Update incoming sequence data and deliver packet notifications
|
||||||
// Packet is only accepted if both the incoming sequence number and incoming ack data are valid
|
// Packet is only accepted if both the incoming sequence number and incoming ack data are valid
|
||||||
PacketNotify.Update(header, (ackedSequence, delivered) =>
|
PacketNotify.Update(header, new PacketNotifyUpdateContext(_packetNotifyUpdateDelegate, channelsToClose));
|
||||||
{
|
|
||||||
// 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)
|
// Extra information associated with the header (read only after acks have been processed)
|
||||||
if (packetSequenceDelta > 0 && !ReadPacketInfo(reader, bHasPacketInfoPayload))
|
if (packetSequenceDelta > 0 && !ReadPacketInfo(reader, bHasPacketInfoPayload))
|
||||||
{
|
{
|
||||||
// TODO: Close connection.
|
|
||||||
Logger.Fatal("Failed to read PacketHeader");
|
Logger.Fatal("Failed to read PacketHeader");
|
||||||
|
Close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -698,16 +729,16 @@ public abstract class UNetConnection : UPlayer
|
|||||||
|
|
||||||
if (bunch.bReliable)
|
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
|
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)
|
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]))
|
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)
|
private bool ReadPacketInfo(FBitReader reader, bool bHasPacketInfoPayload)
|
||||||
{
|
{
|
||||||
if (!bHasPacketInfoPayload)
|
if (!bHasPacketInfoPayload)
|
||||||
@@ -1166,7 +1250,10 @@ public abstract class UNetConnection : UPlayer
|
|||||||
// TOOD: Increment things
|
// TOOD: Increment things
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_bAutoFlush)
|
||||||
|
{
|
||||||
FlushNet();
|
FlushNet();
|
||||||
|
}
|
||||||
|
|
||||||
return bunch.PacketId;
|
return bunch.PacketId;
|
||||||
}
|
}
|
||||||
@@ -1420,7 +1507,12 @@ public abstract class UNetConnection : UPlayer
|
|||||||
NumPaddingBits = 0;
|
NumPaddingBits = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FlushNet()
|
public void SetPlayerOnlinePlatformName(FName inPlayerOnlinePlatformName)
|
||||||
|
{
|
||||||
|
_playerOnlinePlatformName = inPlayerOnlinePlatformName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FlushNet(bool bIgnoreSimulation = false)
|
||||||
{
|
{
|
||||||
if (Driver == null)
|
if (Driver == null)
|
||||||
{
|
{
|
||||||
@@ -1554,7 +1646,7 @@ public abstract class UNetConnection : UPlayer
|
|||||||
_channelsToTick.Remove(channel);
|
_channelsToTick.Remove(channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
private protected void SetClientLoginState(EClientLoginState newState)
|
internal void SetClientLoginState(EClientLoginState newState)
|
||||||
{
|
{
|
||||||
if (ClientLoginState == newState)
|
if (ClientLoginState == newState)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -34,9 +34,12 @@ public abstract class UNetDriver : IAsyncDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// From Engine ini
|
// TODO: From Engine ini
|
||||||
public float KeepAliveTime { get; } = 0.2f;
|
public float KeepAliveTime { get; } = 0.2f;
|
||||||
|
|
||||||
|
// TODO: From Engine ini
|
||||||
|
public int MaxClientRate { get; } = 100000;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// World this net driver is associated with
|
/// World this net driver is associated with
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -80,4 +80,14 @@ public class UPackageMap
|
|||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ReceivedAck(int ackPacketId)
|
||||||
|
{
|
||||||
|
// TODO: Implement
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReceivedNak(int nakPacketId)
|
||||||
|
{
|
||||||
|
// TODO: Implement
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using Prospect.Unreal.Core;
|
||||||
|
using Prospect.Unreal.Net.Actors;
|
||||||
|
|
||||||
|
namespace Prospect.Unreal.Runtime;
|
||||||
|
|
||||||
|
public class UGameInstance
|
||||||
|
{
|
||||||
|
public AGameModeBase? CreateGameModeForURL(FUrl inUrl, UWorld inWorld)
|
||||||
|
{
|
||||||
|
// TODO: World.SpawnActor
|
||||||
|
return new AGameModeBase();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,4 +3,6 @@
|
|||||||
public class UPlayer
|
public class UPlayer
|
||||||
{
|
{
|
||||||
public APlayerController? PlayerController { get; set; }
|
public APlayerController? PlayerController { get; set; }
|
||||||
|
|
||||||
|
public int CurrentNetSpeed { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
using Prospect.Unreal.Core;
|
using Prospect.Unreal.Core;
|
||||||
|
using Prospect.Unreal.Core.Names;
|
||||||
using Prospect.Unreal.Exceptions;
|
using Prospect.Unreal.Exceptions;
|
||||||
using Prospect.Unreal.Net;
|
using Prospect.Unreal.Net;
|
||||||
|
using Prospect.Unreal.Net.Actors;
|
||||||
using Prospect.Unreal.Net.Channels;
|
using Prospect.Unreal.Net.Channels;
|
||||||
using Prospect.Unreal.Net.Packets.Bunch;
|
using Prospect.Unreal.Net.Packets.Bunch;
|
||||||
using Prospect.Unreal.Net.Packets.Control;
|
using Prospect.Unreal.Net.Packets.Control;
|
||||||
@@ -12,9 +14,14 @@ public abstract class UWorld : FNetworkNotify, IAsyncDisposable
|
|||||||
{
|
{
|
||||||
private static readonly ILogger Logger = Log.ForContext<UWorld>();
|
private static readonly ILogger Logger = Log.ForContext<UWorld>();
|
||||||
|
|
||||||
|
private UGameInstance? _owningGameInstance;
|
||||||
|
private AGameModeBase? _authorityGameMode;
|
||||||
|
|
||||||
public UWorld(FUrl url)
|
public UWorld(FUrl url)
|
||||||
{
|
{
|
||||||
Url = url;
|
Url = url;
|
||||||
|
_owningGameInstance = null;
|
||||||
|
_authorityGameMode = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FUrl Url { get; }
|
public FUrl Url { get; }
|
||||||
@@ -29,6 +36,44 @@ public abstract class UWorld : FNetworkNotify, IAsyncDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetGameInstance(UGameInstance instance)
|
||||||
|
{
|
||||||
|
_owningGameInstance = instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UGameInstance GetGameInstance()
|
||||||
|
{
|
||||||
|
if (_owningGameInstance == null)
|
||||||
|
{
|
||||||
|
throw new UnrealException($"Attempted to retrieve null {nameof(UGameInstance)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return _owningGameInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetGameMode(FUrl worldUrl)
|
||||||
|
{
|
||||||
|
if (IsServer() && _authorityGameMode == null)
|
||||||
|
{
|
||||||
|
_authorityGameMode = GetGameInstance().CreateGameModeForURL(worldUrl, this);
|
||||||
|
|
||||||
|
if (_authorityGameMode != null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Error("Failed to spawn GameMode actor");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AGameModeBase? GetAuthGameMode()
|
||||||
|
{
|
||||||
|
return _authorityGameMode;
|
||||||
|
}
|
||||||
|
|
||||||
public bool Listen()
|
public bool Listen()
|
||||||
{
|
{
|
||||||
if (NetDriver != null)
|
if (NetDriver != null)
|
||||||
@@ -114,8 +159,12 @@ public abstract class UWorld : FNetworkNotify, IAsyncDisposable
|
|||||||
{
|
{
|
||||||
case NMT.Hello:
|
case NMT.Hello:
|
||||||
{
|
{
|
||||||
|
const int localNetworkVersion = 0;
|
||||||
|
|
||||||
if (NMT_Hello.Receive(bunch, out var isLittleEndian, out var remoteNetworkVersion, out var encryptionToken))
|
if (NMT_Hello.Receive(bunch, out var isLittleEndian, out var remoteNetworkVersion, out var encryptionToken))
|
||||||
{
|
{
|
||||||
|
Logger.Information("Client connecting with version. LocalNetworkVersion: {Local}, RemoteNetworkVersion: {Remote}", localNetworkVersion, remoteNetworkVersion);
|
||||||
|
|
||||||
// TODO: Version check.
|
// TODO: Version check.
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(encryptionToken))
|
if (string.IsNullOrEmpty(encryptionToken))
|
||||||
@@ -130,9 +179,120 @@ public abstract class UWorld : FNetworkNotify, IAsyncDisposable
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case NMT.Netspeed:
|
||||||
|
{
|
||||||
|
if (NMT_Netspeed.Receive(bunch, out var rate))
|
||||||
|
{
|
||||||
|
connection.CurrentNetSpeed = Math.Clamp(rate, 1800, NetDriver.MaxClientRate);
|
||||||
|
Logger.Debug("Client netspeed is {Num}", connection.CurrentNetSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case NMT.Abort:
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case NMT.Skip:
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case NMT.Login:
|
case NMT.Login:
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
// Admit or deny the player here.
|
||||||
|
if (NMT_Login.Receive(bunch, out var clientResponse, out var requestUrl, out var uniqueIdRepl, out var onlinePlatformName))
|
||||||
|
{
|
||||||
|
connection.ClientResponse = clientResponse;
|
||||||
|
|
||||||
|
// Only the options/portal for the URL should be used during join
|
||||||
|
var newRequestUrl = requestUrl;
|
||||||
|
|
||||||
|
var oneIndex = requestUrl.IndexOf('?');
|
||||||
|
var twoIndex = requestUrl.IndexOf('#');
|
||||||
|
|
||||||
|
if (oneIndex != -1 && twoIndex != -1)
|
||||||
|
{
|
||||||
|
newRequestUrl = requestUrl.Substring(Math.Min(oneIndex, twoIndex));
|
||||||
|
}
|
||||||
|
else if (oneIndex != -1)
|
||||||
|
{
|
||||||
|
newRequestUrl = requestUrl.Substring(oneIndex);
|
||||||
|
}
|
||||||
|
else if (twoIndex != -1)
|
||||||
|
{
|
||||||
|
newRequestUrl = requestUrl.Substring(twoIndex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newRequestUrl = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Debug("Login request: {RequestUrl} userId: {UserId} platform: {Platform}", newRequestUrl, uniqueIdRepl.ToDebugString(), onlinePlatformName);
|
||||||
|
|
||||||
|
// Compromise for passing splitscreen playercount through to gameplay login code,
|
||||||
|
// without adding a lot of extra unnecessary complexity throughout the login code.
|
||||||
|
// NOTE: This code differs from NMT_JoinSplit, by counting + 1 for SplitscreenCount
|
||||||
|
// (since this is the primary connection, not counted in Children)
|
||||||
|
// TODO: Implement proper FUrl constructor
|
||||||
|
var inUrl = new FUrl
|
||||||
|
{
|
||||||
|
Map = Url.Map + newRequestUrl
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!inUrl.Valid)
|
||||||
|
{
|
||||||
|
requestUrl = newRequestUrl;
|
||||||
|
Logger.Error("NMT_Login: Invalid URL {Url}", requestUrl);
|
||||||
|
bunch.SetError();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var splitscreenCount = Math.Min(connection.Children.Count + 1, 255);
|
||||||
|
|
||||||
|
// Don't allow clients to specify this value
|
||||||
|
inUrl.Options.Remove("SplitscreenCount");
|
||||||
|
inUrl.Options.Add($"SplitscreenCount={splitscreenCount}");
|
||||||
|
|
||||||
|
requestUrl = inUrl.ToString();
|
||||||
|
|
||||||
|
// skip to the first option in the URL
|
||||||
|
var tmp = requestUrl.Substring(requestUrl.IndexOf('?'));
|
||||||
|
|
||||||
|
// keep track of net id for player associated with remote connection
|
||||||
|
connection.PlayerId = uniqueIdRepl;
|
||||||
|
|
||||||
|
// keep track of the online platform the player associated with this connection is using.
|
||||||
|
connection.SetPlayerOnlinePlatformName(new FName(onlinePlatformName));
|
||||||
|
|
||||||
|
// ask the game code if this player can join
|
||||||
|
string errorMsg = null;
|
||||||
|
|
||||||
|
var gameMode = GetAuthGameMode();
|
||||||
|
if (gameMode != null)
|
||||||
|
{
|
||||||
|
gameMode.PreLogin(tmp, connection.LowLevelGetRemoteAddress(), connection.PlayerId, out errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(errorMsg))
|
||||||
|
{
|
||||||
|
Logger.Debug("PreLogin failure: {Error}", errorMsg);
|
||||||
|
NMT_Failure.Send(connection, errorMsg);
|
||||||
|
connection.FlushNet(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WelcomePlayer(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
connection.ClientResponse = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -143,6 +303,32 @@ public abstract class UWorld : FNetworkNotify, IAsyncDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void WelcomePlayer(UNetConnection connection)
|
||||||
|
{
|
||||||
|
// TODO: Properly fetch level name from CurrentLevel
|
||||||
|
var levelName = "/Game/ThirdPersonCPP/Maps/ThirdPersonExampleMap";
|
||||||
|
|
||||||
|
// TODO: Properly fetch from AuthorityGameMode
|
||||||
|
var gameName = "/Script/ThirdPersonMP.ThirdPersonMPGameMode";
|
||||||
|
var redirectUrl = string.Empty;
|
||||||
|
|
||||||
|
NMT_Welcome.Send(connection, levelName, gameName, redirectUrl);
|
||||||
|
|
||||||
|
connection.FlushNet();
|
||||||
|
// connection.QueuedBits = 0;
|
||||||
|
connection.SetClientLoginState(EClientLoginState.Welcomed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsServer()
|
||||||
|
{
|
||||||
|
if (NetDriver != null)
|
||||||
|
{
|
||||||
|
return NetDriver.IsServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public async ValueTask DisposeAsync()
|
public async ValueTask DisposeAsync()
|
||||||
{
|
{
|
||||||
if (NetDriver != null)
|
if (NetDriver != null)
|
||||||
|
|||||||
Reference in New Issue
Block a user