Implement handshake Hello NetControlMessage, recv and send
This commit is contained in:
@@ -1,8 +1,13 @@
|
|||||||
# Agent
|
# Agent
|
||||||
Prospect.Agent.dll
|
Prospect.Agent.dll
|
||||||
|
|
||||||
|
# Api
|
||||||
appsettings.Development.json
|
appsettings.Development.json
|
||||||
appsettings.Production.json
|
appsettings.Production.json
|
||||||
|
|
||||||
|
# Unreal
|
||||||
|
Prospect.Unreal/Generated
|
||||||
|
|
||||||
## Ignore Visual Studio temporary files, build results, and
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
## files generated by popular Visual Studio add-ons.
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Prospect.Unreal.Generator
|
||||||
|
{
|
||||||
|
internal class NetControlMessageEntry
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public int Index { get; set; }
|
||||||
|
|
||||||
|
public List<string> Params { get; set; } = new List<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
|
||||||
|
namespace Prospect.Unreal.Generator
|
||||||
|
{
|
||||||
|
internal class NetControlMessageParser
|
||||||
|
{
|
||||||
|
internal IReadOnlyList<NetControlMessageEntry> Parse(ImmutableArray<AttributeSyntax> attributes)
|
||||||
|
{
|
||||||
|
var entries = new List<NetControlMessageEntry>();
|
||||||
|
|
||||||
|
foreach (var attribute in attributes)
|
||||||
|
{
|
||||||
|
var attributeParams = attribute.ArgumentList.Arguments;
|
||||||
|
var attributeEntry = new NetControlMessageEntry();
|
||||||
|
|
||||||
|
attributeEntry.Name = ((LiteralExpressionSyntax)attributeParams[0].Expression).Token.ValueText; // StringLiteralExpression
|
||||||
|
attributeEntry.Index = (int)((LiteralExpressionSyntax)attributeParams[1].Expression).Token.Value; // NumericLiteralExpression
|
||||||
|
|
||||||
|
for (int i = 2; i < attributeParams.Count; i++)
|
||||||
|
{
|
||||||
|
var paramType = ((TypeOfExpressionSyntax)attributeParams[i].Expression);
|
||||||
|
var paramKeyword = paramType.Type;
|
||||||
|
if (paramKeyword is IdentifierNameSyntax paramIdentifierName)
|
||||||
|
{
|
||||||
|
attributeEntry.Params.Add(paramIdentifierName.Identifier.ValueText);
|
||||||
|
}
|
||||||
|
else if (paramKeyword is PredefinedTypeSyntax predefinedTypeSyntax)
|
||||||
|
{
|
||||||
|
attributeEntry.Params.Add(predefinedTypeSyntax.Keyword.ValueText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.Add(attributeEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
using Prospect.Unreal.Generator.Util;
|
||||||
|
using Scriban;
|
||||||
|
using Scriban.Runtime;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Prospect.Unreal.Generator
|
||||||
|
{
|
||||||
|
[Generator]
|
||||||
|
internal class NetControlMessageSourceIncrementalGenerator : IIncrementalGenerator
|
||||||
|
{
|
||||||
|
public const string Namespace = "Prospect.Unreal.Net.Packets.Control";
|
||||||
|
public const string AttributeName = "NetControlMessage";
|
||||||
|
|
||||||
|
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||||
|
{
|
||||||
|
var attributes = context.SyntaxProvider
|
||||||
|
.CreateSyntaxProvider(
|
||||||
|
predicate: IsSyntaxTargetForGeneration,
|
||||||
|
transform: GetSemanticTargetForGeneration)
|
||||||
|
.Where(static m => m is not null);
|
||||||
|
|
||||||
|
var compilationAndAttributes = context.CompilationProvider.Combine(attributes.Collect());
|
||||||
|
|
||||||
|
context.RegisterSourceOutput(compilationAndAttributes, static (spc, source) => Execute(source.Left, source.Right, spc));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsSyntaxTargetForGeneration(SyntaxNode node, CancellationToken _) =>
|
||||||
|
node is AttributeSyntax attribute && attribute.Name is IdentifierNameSyntax;
|
||||||
|
|
||||||
|
private static AttributeSyntax GetSemanticTargetForGeneration(GeneratorSyntaxContext context, CancellationToken _)
|
||||||
|
{
|
||||||
|
var attribute = (AttributeSyntax)context.Node;
|
||||||
|
var attributeName = (IdentifierNameSyntax)attribute.Name;
|
||||||
|
|
||||||
|
if (attributeName.Identifier.ValueText != AttributeName)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return attribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Execute(Compilation compilation, ImmutableArray<AttributeSyntax> attributes, SourceProductionContext context)
|
||||||
|
{
|
||||||
|
var parser = new NetControlMessageParser();
|
||||||
|
var results = parser.Parse(attributes);
|
||||||
|
|
||||||
|
if (results.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.AddSource("NMTEnum.g.cs", GenerateEnum(results));
|
||||||
|
|
||||||
|
foreach (var result in results)
|
||||||
|
{
|
||||||
|
context.AddSource($"NMT_{result.Name}.g.cs", GenerateMessage(result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GenerateEnum(IReadOnlyList<NetControlMessageEntry> entries)
|
||||||
|
{
|
||||||
|
var templateData = EmbeddedResource.GetContent("Templates/NMTEnum.sbntxt");
|
||||||
|
var template = Template.Parse(templateData);
|
||||||
|
|
||||||
|
return template.Render(new
|
||||||
|
{
|
||||||
|
cnamespace = Namespace,
|
||||||
|
entries = entries
|
||||||
|
}, member => member.Name).Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GenerateMessage(NetControlMessageEntry entry)
|
||||||
|
{
|
||||||
|
var scriptObject = new ScriptObject();
|
||||||
|
|
||||||
|
scriptObject.Import(typeof(ScribanFunctions));
|
||||||
|
scriptObject.Add("cnamespace", Namespace);
|
||||||
|
scriptObject.Add("entry", entry);
|
||||||
|
|
||||||
|
var context = new TemplateContext();
|
||||||
|
|
||||||
|
context.PushGlobal(scriptObject);
|
||||||
|
|
||||||
|
var templateData = EmbeddedResource.GetContent("Templates/NMTMessage.sbntxt");
|
||||||
|
var template = Template.Parse(templateData);
|
||||||
|
|
||||||
|
return template.Render(scriptObject, member => member.Name).Trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static class ScribanFunctions
|
||||||
|
{
|
||||||
|
[ScriptMemberIgnore]
|
||||||
|
private static readonly Dictionary<string, ParamDef> TypeDefinitions = new Dictionary<string, ParamDef>
|
||||||
|
{
|
||||||
|
{ "byte", new ParamDef("bunch.ReadByte()", "bunch.WriteByte({0})") },
|
||||||
|
{ "int", new ParamDef("bunch.ReadInt32()", "bunch.WriteInt32({0})") },
|
||||||
|
{ "uint", new ParamDef("bunch.ReadUInt32()", "bunch.WriteUInt32({0})") },
|
||||||
|
{ "FString", new ParamDef("bunch.ReadString()", "bunch.WriteString({0})") },
|
||||||
|
};
|
||||||
|
|
||||||
|
public static string SendParams(List<string> args)
|
||||||
|
{
|
||||||
|
return MethodParameter(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ReadOutParams(List<string> args)
|
||||||
|
{
|
||||||
|
return MethodParameter(args, "out");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ReadOut(string paramType, int index)
|
||||||
|
{
|
||||||
|
var paramName = (char)('a' + index);
|
||||||
|
var paramRead = ReadType(paramType);
|
||||||
|
if (paramRead.StartsWith("throw")) {
|
||||||
|
return paramRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"{paramName} = {paramRead}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string SendType(string paramType, int index)
|
||||||
|
{
|
||||||
|
if (TypeDefinitions.TryGetValue(paramType, out var type))
|
||||||
|
{
|
||||||
|
return string.Format(type.Write, (char)('a' + index));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"throw new NotImplementedException(\"Unsupported type {paramType}\")";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ReadType(string param)
|
||||||
|
{
|
||||||
|
if (TypeDefinitions.TryGetValue(param, out var type))
|
||||||
|
{
|
||||||
|
return type.Read;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"throw new NotImplementedException(\"Unsupported type {param}\")";
|
||||||
|
}
|
||||||
|
|
||||||
|
[ScriptMemberIgnore]
|
||||||
|
private static string MethodParameter(List<string> args, string prefix = null)
|
||||||
|
{
|
||||||
|
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
|
for (int i = 0; i < args.Count; i++)
|
||||||
|
{
|
||||||
|
if (i < args.Count)
|
||||||
|
{
|
||||||
|
builder.Append(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
var paramName = (char)('a' + i);
|
||||||
|
var paramType = args[i];
|
||||||
|
if (paramType == "FString")
|
||||||
|
{
|
||||||
|
paramType = "string";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prefix != null)
|
||||||
|
{
|
||||||
|
builder.AppendFormat("{0} {1} {2}", prefix, paramType, paramName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.AppendFormat("{0} {1}", paramType, paramName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Prospect.Unreal.Generator
|
||||||
|
{
|
||||||
|
internal class NetControlMessageSyntaxReceiver : ISyntaxContextReceiver
|
||||||
|
{
|
||||||
|
public const string AttributeName = "NetControlMessage";
|
||||||
|
|
||||||
|
public List<NetControlMessageEntry> Entries { get; } = new List<NetControlMessageEntry>();
|
||||||
|
|
||||||
|
public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
|
||||||
|
{
|
||||||
|
if (context.Node is not AttributeSyntax attribute)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attribute.Name is not IdentifierNameSyntax identifierName)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (identifierName.Identifier.ValueText != AttributeName)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var attributeParams = attribute.ArgumentList.Arguments;
|
||||||
|
var attributeEntry = new NetControlMessageEntry();
|
||||||
|
|
||||||
|
attributeEntry.Name = ((LiteralExpressionSyntax)attributeParams[0].Expression).Token.ValueText; // StringLiteralExpression
|
||||||
|
attributeEntry.Index = (int) ((LiteralExpressionSyntax)attributeParams[1].Expression).Token.Value; // NumericLiteralExpression
|
||||||
|
|
||||||
|
for (int i = 2; i < attributeParams.Count; i++)
|
||||||
|
{
|
||||||
|
var paramType = ((TypeOfExpressionSyntax)attributeParams[i].Expression);
|
||||||
|
var paramKeyword = paramType.Type;
|
||||||
|
if (paramKeyword is IdentifierNameSyntax paramIdentifierName)
|
||||||
|
{
|
||||||
|
attributeEntry.Params.Add(paramIdentifierName.Identifier.ValueText);
|
||||||
|
}
|
||||||
|
else if (paramKeyword is PredefinedTypeSyntax predefinedTypeSyntax)
|
||||||
|
{
|
||||||
|
attributeEntry.Params.Add(predefinedTypeSyntax.Keyword.ValueText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Entries.Add(attributeEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"Roslyn": {
|
||||||
|
"commandName": "DebugRoslynComponent",
|
||||||
|
"targetProject": "..\\Prospect.Unreal\\Prospect.Unreal.csproj"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<LangVersion>9.0</LangVersion>
|
||||||
|
<IsRoslynComponent>true</IsRoslynComponent>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="Templates\*.sbntxt" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="Templates\NMTMessage.sbntxt" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all" />
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Scriban" Version="2.1.2" GeneratePathProperty="true" PrivateAssets="all" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<GetTargetPathDependsOn>$(GetTargetPathDependsOn);GetDependencyTargetPaths</GetTargetPathDependsOn>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<Target Name="GetDependencyTargetPaths">
|
||||||
|
<ItemGroup>
|
||||||
|
<TargetPathWithTargetPlatformMoniker Include="$(PKGScriban)\lib\netstandard2.0\Scriban.dll" IncludeRuntimeDependency="false" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace {{ cnamespace }};
|
||||||
|
|
||||||
|
public enum NMT
|
||||||
|
{
|
||||||
|
{{~ for entry in entries ~}}
|
||||||
|
{{ entry.Name }} = {{ entry.Index }},
|
||||||
|
{{~ end ~}}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using Prospect.Unreal.Core;
|
||||||
|
using Prospect.Unreal.Net;
|
||||||
|
using Prospect.Unreal.Net.Packets.Bunch;
|
||||||
|
|
||||||
|
namespace {{ cnamespace }};
|
||||||
|
|
||||||
|
public static class NMT_{{ entry.Name }}
|
||||||
|
{
|
||||||
|
public static void Send(UNetConnection conn{{send_params entry.Params}})
|
||||||
|
{
|
||||||
|
if (conn.Channels[0] != null && !conn.Channels[0].Closing)
|
||||||
|
{
|
||||||
|
using var bunch = new FControlChannelOutBunch(conn.Channels[0], false);
|
||||||
|
bunch.WriteByte({{ entry.Index }});
|
||||||
|
{{~ for param in entry.Params ~}}
|
||||||
|
{{send_type param for.index}};
|
||||||
|
{{~ end ~}}
|
||||||
|
conn.Channels[0].SendBunch(bunch, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool Receive(FInBunch bunch{{read_out_params entry.Params}})
|
||||||
|
{
|
||||||
|
{{~ for param in entry.Params ~}}
|
||||||
|
{{read_out param for.index}};
|
||||||
|
{{~ end ~}}
|
||||||
|
return !bunch.IsError();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Discard(FInBunch bunch)
|
||||||
|
{
|
||||||
|
{{~ for param in entry.Params ~}}
|
||||||
|
{{read_type param}};
|
||||||
|
{{~ end ~}}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Prospect.Unreal.Generator.Util
|
||||||
|
{
|
||||||
|
internal static class EmbeddedResource
|
||||||
|
{
|
||||||
|
public static string GetContent(string relativePath)
|
||||||
|
{
|
||||||
|
var baseName = Assembly.GetExecutingAssembly().GetName().Name;
|
||||||
|
var resourceName = relativePath
|
||||||
|
.TrimStart('.')
|
||||||
|
.Replace(Path.DirectorySeparatorChar, '.')
|
||||||
|
.Replace(Path.AltDirectorySeparatorChar, '.');
|
||||||
|
|
||||||
|
using var stream = Assembly.GetExecutingAssembly()
|
||||||
|
.GetManifestResourceStream(baseName + "." + resourceName);
|
||||||
|
|
||||||
|
if (stream == null)
|
||||||
|
throw new NotSupportedException();
|
||||||
|
|
||||||
|
using var reader = new StreamReader(stream);
|
||||||
|
return reader.ReadToEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Prospect.Unreal.Generator.Util
|
||||||
|
{
|
||||||
|
internal class ParamDef
|
||||||
|
{
|
||||||
|
public ParamDef(string read, string write)
|
||||||
|
{
|
||||||
|
Read = read;
|
||||||
|
Write = write;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Read { get; }
|
||||||
|
public string Write { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Prospect.Unreal.Core;
|
||||||
|
|
||||||
|
public static class FPlatformTime
|
||||||
|
{
|
||||||
|
public static double Seconds()
|
||||||
|
{
|
||||||
|
return DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Prospect.Unreal.Core;
|
||||||
|
|
||||||
|
public class UObject
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
using Prospect.Unreal.Core.Names;
|
using Prospect.Unreal.Core.Names;
|
||||||
using Prospect.Unreal.Net.Packets.Bunch;
|
using Prospect.Unreal.Net.Packets.Bunch;
|
||||||
|
|
||||||
namespace Prospect.Unreal.Net.Channels;
|
namespace Prospect.Unreal.Net.Channels.Actor;
|
||||||
|
|
||||||
public class UActorChannel : UChannel
|
public class UActorChannel : UChannel
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
using Prospect.Unreal.Core.Names;
|
||||||
|
using Prospect.Unreal.Net.Packets.Bunch;
|
||||||
|
using Prospect.Unreal.Net.Packets.Control;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace Prospect.Unreal.Net.Channels.Control;
|
||||||
|
|
||||||
|
public class UControlChannel : UChannel
|
||||||
|
{
|
||||||
|
private static readonly ILogger Logger = Log.ForContext<UControlChannel>();
|
||||||
|
|
||||||
|
public UControlChannel()
|
||||||
|
{
|
||||||
|
ChType = EChannelType.CHTYPE_Control;
|
||||||
|
ChName = UnrealNames.FNames[UnrealNameKey.Control];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ReceivedBunch(FInBunch bunch)
|
||||||
|
{
|
||||||
|
// if (Connection != null && bNeedsEndianInspection && !CheckEndianess(bunch))
|
||||||
|
// {
|
||||||
|
// // Send close bunch and shutdown this connection
|
||||||
|
// }
|
||||||
|
|
||||||
|
// bunch.()
|
||||||
|
|
||||||
|
while (!bunch.AtEnd() && Connection != null && Connection.State != EConnectionState.USOCK_Closed)
|
||||||
|
{
|
||||||
|
var messageType = (NMT) bunch.ReadByte();
|
||||||
|
|
||||||
|
if (bunch.IsError())
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pos = bunch.GetPosBits();
|
||||||
|
|
||||||
|
if (messageType == NMT.ActorChannelFailure)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
else if (messageType == NMT.GameSpecific)
|
||||||
|
{
|
||||||
|
// the most common Notify handlers do not support subclasses by default and
|
||||||
|
// so we redirect the game specific messaging to the GameInstance instead
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
else if (messageType == NMT.SecurityViolation)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
else if (messageType == NMT.DestructionInfo)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Process control message on client/server connection
|
||||||
|
Connection.Driver!.Notify.NotifyControlMessage(Connection, messageType, bunch);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the message was not handled, eat it ourselves
|
||||||
|
if (pos == bunch.GetPosBits() && !bunch.IsError())
|
||||||
|
{
|
||||||
|
switch (messageType)
|
||||||
|
{
|
||||||
|
case NMT.Hello:
|
||||||
|
NMT_Hello.Discard(bunch);
|
||||||
|
break;
|
||||||
|
case NMT.Welcome:
|
||||||
|
NMT_Welcome.Discard(bunch);
|
||||||
|
break;
|
||||||
|
case NMT.Upgrade:
|
||||||
|
NMT_Upgrade.Discard(bunch);
|
||||||
|
break;
|
||||||
|
case NMT.Challenge:
|
||||||
|
NMT_Challenge.Discard(bunch);
|
||||||
|
break;
|
||||||
|
case NMT.Netspeed:
|
||||||
|
NMT_Netspeed.Discard(bunch);
|
||||||
|
break;
|
||||||
|
case NMT.Login:
|
||||||
|
NMT_Login.Discard(bunch);
|
||||||
|
break;
|
||||||
|
case NMT.Failure:
|
||||||
|
NMT_Failure.Discard(bunch);
|
||||||
|
break;
|
||||||
|
case NMT.Join:
|
||||||
|
NMT_Join.Discard(bunch);
|
||||||
|
break;
|
||||||
|
case NMT.JoinSplit:
|
||||||
|
NMT_JoinSplit.Discard(bunch);
|
||||||
|
break;
|
||||||
|
case NMT.Skip:
|
||||||
|
NMT_Skip.Discard(bunch);
|
||||||
|
break;
|
||||||
|
case NMT.Abort:
|
||||||
|
NMT_Abort.Discard(bunch);
|
||||||
|
break;
|
||||||
|
case NMT.PCSwap:
|
||||||
|
NMT_PCSwap.Discard(bunch);
|
||||||
|
break;
|
||||||
|
case NMT.ActorChannelFailure:
|
||||||
|
NMT_ActorChannelFailure.Discard(bunch);
|
||||||
|
break;
|
||||||
|
case NMT.DebugText:
|
||||||
|
NMT_DebugText.Discard(bunch);
|
||||||
|
break;
|
||||||
|
case NMT.NetGUIDAssign:
|
||||||
|
NMT_NetGUIDAssign.Discard(bunch);
|
||||||
|
break;
|
||||||
|
case NMT.EncryptionAck:
|
||||||
|
NMT_EncryptionAck.Discard(bunch);
|
||||||
|
break;
|
||||||
|
case NMT.BeaconWelcome:
|
||||||
|
NMT_BeaconWelcome.Discard(bunch);
|
||||||
|
break;
|
||||||
|
case NMT.BeaconJoin:
|
||||||
|
NMT_BeaconJoin.Discard(bunch);
|
||||||
|
break;
|
||||||
|
case NMT.BeaconAssignGUID:
|
||||||
|
NMT_BeaconAssignGUID.Discard(bunch);
|
||||||
|
break;
|
||||||
|
case NMT.BeaconNetGUIDAck:
|
||||||
|
NMT_BeaconNetGUIDAck.Discard(bunch);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// if this fails, a case is missing above for an implemented message type
|
||||||
|
// or the connection is being sent potentially malformed packets
|
||||||
|
// @PotentialDOSAttackDetection
|
||||||
|
Logger.Error("Received unknown control channel message {MessageType}. Closing connection", (int)messageType);
|
||||||
|
Connection.Close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bunch.IsError())
|
||||||
|
{
|
||||||
|
Logger.Error("Failed to read control channel message '{MessageType}'", messageType);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bunch.IsError())
|
||||||
|
{
|
||||||
|
Logger.Error("Failed to read control channel message");
|
||||||
|
|
||||||
|
if (Connection != null)
|
||||||
|
{
|
||||||
|
Connection.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override FPacketIdRange SendBunch(FOutBunch bunch, bool merge)
|
||||||
|
{
|
||||||
|
// TODO: Queue.
|
||||||
|
if (!bunch.IsError())
|
||||||
|
{
|
||||||
|
return base.SendBunch(bunch, merge);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// an error here most likely indicates an unfixable error, such as the text using more than the maximum packet size
|
||||||
|
// so there is no point in queueing it as it will just fail again
|
||||||
|
Logger.Error("Control channel bunch overflowed");
|
||||||
|
Connection!.Close();
|
||||||
|
return new FPacketIdRange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using Prospect.Unreal.Core.Names;
|
using Prospect.Unreal.Core.Names;
|
||||||
using Prospect.Unreal.Exceptions;
|
using Prospect.Unreal.Exceptions;
|
||||||
using Prospect.Unreal.Net.Packets.Bunch;
|
using Prospect.Unreal.Net.Packets.Bunch;
|
||||||
|
using Prospect.Unreal.Serialization;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Prospect.Unreal.Net.Channels;
|
namespace Prospect.Unreal.Net.Channels;
|
||||||
@@ -14,7 +15,7 @@ public abstract class UChannel
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Owner connection.
|
/// Owner connection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public UNetConnection Connection { get; set; }
|
public UNetConnection? Connection { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If OpenedLocally is true, this means we have acknowledged the packet we sent the bOpen bunch on.
|
/// If OpenedLocally is true, this means we have acknowledged the packet we sent the bOpen bunch on.
|
||||||
@@ -123,7 +124,10 @@ public abstract class UChannel
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public FInBunch? InRec { get; set; }
|
public FInBunch? InRec { get; set; }
|
||||||
|
|
||||||
// public FOutBunch OutRec { get; set; }
|
/// <summary>
|
||||||
|
/// Outgoing reliable unacked data.
|
||||||
|
/// </summary>
|
||||||
|
public FOutBunch? OutRec { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Partial bunch we are receiving (incoming partial bunches are appended to this).
|
/// Partial bunch we are receiving (incoming partial bunches are appended to this).
|
||||||
@@ -413,8 +417,7 @@ public abstract class UChannel
|
|||||||
// Remember the range.
|
// Remember the range.
|
||||||
// In the case of a non partial, HandleBunch == Bunch
|
// In the case of a non partial, HandleBunch == Bunch
|
||||||
// In the case of a partial, HandleBunch should == InPartialBunch, and Bunch should be the last bunch.
|
// In the case of a partial, HandleBunch should == InPartialBunch, and Bunch should be the last bunch.
|
||||||
OpenPacketId.First = handleBunch.PacketId;
|
OpenPacketId = new FPacketIdRange(handleBunch.PacketId, bunch.PacketId);
|
||||||
OpenPacketId.Last = bunch.PacketId;
|
|
||||||
OpenAcked = true;
|
OpenAcked = true;
|
||||||
|
|
||||||
Logger.Verbose("ReceivedNextBunch: Channel now fully open. ChIndex: {ChIndex}, OpenPacketId.First: {First}, OpenPacketId.Last: {Last}", ChIndex, OpenPacketId.First, OpenPacketId.Last);
|
Logger.Verbose("ReceivedNextBunch: Channel now fully open. ChIndex: {ChIndex}, OpenPacketId.First: {First}, OpenPacketId.Last: {Last}", ChIndex, OpenPacketId.First, OpenPacketId.Last);
|
||||||
@@ -501,6 +504,309 @@ public abstract class UChannel
|
|||||||
|
|
||||||
protected abstract void ReceivedBunch(FInBunch bunch);
|
protected abstract void ReceivedBunch(FInBunch bunch);
|
||||||
|
|
||||||
|
public virtual FPacketIdRange SendBunch(FOutBunch bunch, bool merge)
|
||||||
|
{
|
||||||
|
if (Connection == null || Connection.Driver == null)
|
||||||
|
{
|
||||||
|
throw new UnrealNetException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ChIndex == -1)
|
||||||
|
{
|
||||||
|
return new FPacketIdRange(UnrealConstants.IndexNone);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsBunchTooLarge(Connection!, bunch))
|
||||||
|
{
|
||||||
|
Logger.Error("Attempted to send bunch exceeding max allowed size. BunchSize={Size}, MaximumSize={MaxSize}", bunch.GetNumBytes(), NetMaxConstructedPartialBunchSizeBytes);
|
||||||
|
bunch.SetError();
|
||||||
|
return new FPacketIdRange(UnrealConstants.IndexNone);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Closing || Connection.Channels[ChIndex] != this || bunch.IsError() || bunch.bHasPackageMapExports)
|
||||||
|
{
|
||||||
|
throw new UnrealNetException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set bunch flags.
|
||||||
|
var bDormancyClose = bunch.bClose && (bunch.CloseReason == EChannelCloseReason.Dormancy);
|
||||||
|
|
||||||
|
if (OpenedLocally && ((OpenPacketId.First == UnrealConstants.IndexNone) || ((Connection.ResendAllDataState == EResendAllDataState.None) && !bDormancyClose)))
|
||||||
|
{
|
||||||
|
var bOpenBunch = true;
|
||||||
|
|
||||||
|
if (Connection.ResendAllDataState == EResendAllDataState.SinceCheckpoint)
|
||||||
|
{
|
||||||
|
bOpenBunch = !bOpenedForCheckpoint;
|
||||||
|
bOpenedForCheckpoint = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bOpenBunch)
|
||||||
|
{
|
||||||
|
bunch.bOpen = true;
|
||||||
|
OpenTemporary = !bunch.bReliable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OpenTemporary && bunch.bReliable)
|
||||||
|
{
|
||||||
|
throw new UnrealNetException("Channel was opened temporarily, we are never allowed to send reliable packets on it");
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the max number of bits we can have in a single bunch
|
||||||
|
var MAX_SINGLE_BUNCH_SIZE_BITS = Connection.GetMaxSingleBunchSizeBits();
|
||||||
|
|
||||||
|
// Max bytes we'll put in a partial bunch
|
||||||
|
var MAX_SINGLE_BUNCH_SIZE_BYTES = MAX_SINGLE_BUNCH_SIZE_BITS / 8;
|
||||||
|
|
||||||
|
// Max bits will put in a partial bunch (byte aligned, we dont want to deal with partial bytes in the partial bunches)
|
||||||
|
var MAX_PARTIAL_BUNCH_SIZE_BITS = MAX_SINGLE_BUNCH_SIZE_BYTES * 8;
|
||||||
|
|
||||||
|
var outgoingBunches = new List<FOutBunch>();
|
||||||
|
|
||||||
|
// Add any export bunches
|
||||||
|
// Replay connections will manage export bunches separately.
|
||||||
|
if (!Connection.IsInternalAck())
|
||||||
|
{
|
||||||
|
// TODO: AppendExportBunches
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outgoingBunches.Count != 0)
|
||||||
|
{
|
||||||
|
// Don't merge if we are exporting guid's
|
||||||
|
// We can't be for sure if the last bunch has exported guids as well, so this just simplifies things
|
||||||
|
merge = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Connection.Driver.IsServer())
|
||||||
|
{
|
||||||
|
// Append any "must be mapped" guids to front of bunch from the packagemap
|
||||||
|
// TODO: AppendMustBeMappedGuids
|
||||||
|
|
||||||
|
if (bunch.bHasMustBeMappedGUIDs)
|
||||||
|
{
|
||||||
|
merge = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------
|
||||||
|
// Contemplate merging.
|
||||||
|
//-----------------------------------------------------
|
||||||
|
|
||||||
|
// TODO: Merge
|
||||||
|
// var preExistingBits = 0;
|
||||||
|
FOutBunch? outBunch = null;
|
||||||
|
|
||||||
|
// if (merge
|
||||||
|
// && Connection.LastOut.ChIndex == bunch.ChIndex
|
||||||
|
// && Connection.LastOut.bReliable == bunch.bReliable
|
||||||
|
// && Connection.AllowMerge
|
||||||
|
// && Connection.LastEnd.GetNumBits() != 0
|
||||||
|
// && Connection.LastEnd.GetNumBits() == Connection.SendBuffer.GetNumBits()
|
||||||
|
// && Connection.LastOut.GetNumBits() + bunch.GetNumBits() <= MAX_SINGLE_BUNCH_SIZE_BITS)
|
||||||
|
// {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
|
//-----------------------------------------------------
|
||||||
|
// Possibly split large bunch into list of smaller partial bunches
|
||||||
|
//-----------------------------------------------------
|
||||||
|
if (bunch.GetNumBits() > MAX_SINGLE_BUNCH_SIZE_BITS)
|
||||||
|
{
|
||||||
|
var data = bunch.GetData().AsSpan();
|
||||||
|
var bitsLeft = bunch.GetNumBits();
|
||||||
|
|
||||||
|
merge = false;
|
||||||
|
|
||||||
|
while (bitsLeft > 0)
|
||||||
|
{
|
||||||
|
var partialBunch = new FOutBunch(this, false);
|
||||||
|
var bitsThisBunch = (long) Math.Min(bitsLeft, MAX_PARTIAL_BUNCH_SIZE_BITS);
|
||||||
|
|
||||||
|
partialBunch.SerializeBits(data, bitsThisBunch);
|
||||||
|
|
||||||
|
outgoingBunches.Add(partialBunch);
|
||||||
|
|
||||||
|
bitsLeft -= bitsThisBunch;
|
||||||
|
data = data.Slice((int)(bitsThisBunch >> 3));
|
||||||
|
|
||||||
|
Logger.Debug("Making partial bunch from content bunch. bitsThisBunch: {Bits} bitsLeft: {Left}", bitsThisBunch, bitsLeft);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
outgoingBunches.Add(bunch);
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------
|
||||||
|
// Send all the bunches we need to
|
||||||
|
// Note: this is done all at once. We could queue this up somewhere else before sending to Out.
|
||||||
|
//-----------------------------------------------------
|
||||||
|
var packetIdRange = new FPacketIdRange();
|
||||||
|
var bOverflowsReliable = (NumOutRec + outgoingBunches.Count >= UNetConnection.ReliableBuffer + (bunch.bClose ? 1 : 0));
|
||||||
|
|
||||||
|
if (bunch.bReliable && bOverflowsReliable)
|
||||||
|
{
|
||||||
|
// TODO: Send NMT_Failure
|
||||||
|
// TODO: FlushNet(true);
|
||||||
|
Connection.Close();
|
||||||
|
|
||||||
|
throw new NotImplementedException();
|
||||||
|
return packetIdRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outgoingBunches.Count > 1)
|
||||||
|
{
|
||||||
|
Logger.Debug("Sending {Count} bunches. Channel: {ChIndex} {Channel}", outgoingBunches.Count, bunch.ChIndex, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var partialNum = 0; partialNum < outgoingBunches.Count; partialNum++)
|
||||||
|
{
|
||||||
|
var nextBunch = outgoingBunches[partialNum];
|
||||||
|
|
||||||
|
nextBunch.bReliable = bunch.bReliable;
|
||||||
|
nextBunch.bOpen = bunch.bOpen;
|
||||||
|
nextBunch.bClose = bunch.bClose;
|
||||||
|
nextBunch.bDormant = bunch.bDormant;
|
||||||
|
nextBunch.CloseReason = bunch.CloseReason;
|
||||||
|
nextBunch.bIsReplicationPaused = bunch.bIsReplicationPaused;
|
||||||
|
nextBunch.ChIndex = bunch.ChIndex;
|
||||||
|
nextBunch.ChType = bunch.ChType;
|
||||||
|
nextBunch.ChName = bunch.ChName;
|
||||||
|
|
||||||
|
if (!nextBunch.bHasPackageMapExports)
|
||||||
|
{
|
||||||
|
nextBunch.bHasMustBeMappedGUIDs |= bunch.bHasMustBeMappedGUIDs;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outgoingBunches.Count > 1)
|
||||||
|
{
|
||||||
|
nextBunch.bPartial = true;
|
||||||
|
nextBunch.bPartialInitial = partialNum == 0;
|
||||||
|
nextBunch.bPartialFinal = partialNum == outgoingBunches.Count - 1;
|
||||||
|
nextBunch.bOpen &= partialNum == 0; // Only the first bunch should have the bOpen bit set
|
||||||
|
nextBunch.bClose = (bunch.bClose && (outgoingBunches.Count - 1 == partialNum)); // Only last bunch should have bClose bit set
|
||||||
|
}
|
||||||
|
|
||||||
|
var thisOutBunch = PrepBunch(nextBunch, ref outBunch, merge);
|
||||||
|
|
||||||
|
// Update Packet Range
|
||||||
|
var packetId = SendRawBunch(thisOutBunch, merge);
|
||||||
|
if (partialNum == 0)
|
||||||
|
{
|
||||||
|
packetIdRange = new FPacketIdRange(packetId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
packetIdRange = new FPacketIdRange(packetIdRange.First, packetId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update channel sequence count.
|
||||||
|
Connection.LastOut = thisOutBunch;
|
||||||
|
Connection.LastEnd = new FBitWriterMark(Connection.SendBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update open range if necessary
|
||||||
|
if (bunch.bOpen && (Connection.ResendAllDataState == EResendAllDataState.None))
|
||||||
|
{
|
||||||
|
OpenPacketId = packetIdRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy outgoing bunches now that they are sent, except the one that was passed into ::SendBunch
|
||||||
|
// This is because the one passed in ::SendBunch is the responsibility of the caller, the other bunches in OutgoingBunches
|
||||||
|
// were either allocated in this function for partial bunches, or taken from the package map, which expects us to destroy them.
|
||||||
|
foreach (var deleteBunch in outgoingBunches)
|
||||||
|
{
|
||||||
|
if (deleteBunch != bunch)
|
||||||
|
{
|
||||||
|
deleteBunch.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return packetIdRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int SendRawBunch(FOutBunch outBunch, bool merge)
|
||||||
|
{
|
||||||
|
// Send the raw bunch.
|
||||||
|
outBunch.ReceivedAck = false;
|
||||||
|
|
||||||
|
var packetId = Connection!.SendRawBunch(outBunch, merge);
|
||||||
|
|
||||||
|
if (OpenPacketId.First == UnrealConstants.IndexNone && OpenedLocally)
|
||||||
|
{
|
||||||
|
OpenPacketId = new FPacketIdRange(packetId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outBunch.bClose)
|
||||||
|
{
|
||||||
|
SetClosingFlag();
|
||||||
|
}
|
||||||
|
|
||||||
|
return packetId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private FOutBunch PrepBunch(FOutBunch bunch, ref FOutBunch? outBunch, bool merge)
|
||||||
|
{
|
||||||
|
if (Connection!.ResendAllDataState != EResendAllDataState.None)
|
||||||
|
{
|
||||||
|
return bunch;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find outgoing bunch index.
|
||||||
|
if (bunch.bReliable)
|
||||||
|
{
|
||||||
|
// Find spot, which was guaranteed available by FOutBunch constructor.
|
||||||
|
if (outBunch == null)
|
||||||
|
{
|
||||||
|
if (!(NumOutRec < UNetConnection.ReliableBuffer - 1 + (bunch.bClose ? 1 : 0)))
|
||||||
|
{
|
||||||
|
Logger.Warning("PrepBunch: Reliable buffer overflow! {Channel}", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
bunch.Next = null;
|
||||||
|
bunch.ChSequence = ++Connection.OutReliable[ChIndex];
|
||||||
|
NumOutRec++;
|
||||||
|
// TODO: Verify below works as expected
|
||||||
|
outBunch = new FOutBunch(bunch);
|
||||||
|
var outLink = OutRec;
|
||||||
|
|
||||||
|
while (outLink != null)
|
||||||
|
{
|
||||||
|
outLink = outLink.Next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outLink == null)
|
||||||
|
{
|
||||||
|
OutRec = outBunch;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
outLink.Next = outBunch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bunch.Next = outBunch.Next;
|
||||||
|
outBunch = bunch;
|
||||||
|
}
|
||||||
|
|
||||||
|
Connection.LastOutBunch = outBunch;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
outBunch = bunch;
|
||||||
|
Connection.LastOutBunch = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return outBunch;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetClosingFlag()
|
||||||
|
{
|
||||||
|
Closing = true;
|
||||||
|
}
|
||||||
|
|
||||||
public void ConditionalCleanUp(bool bForDestroy, EChannelCloseReason closeReason)
|
public void ConditionalCleanUp(bool bForDestroy, EChannelCloseReason closeReason)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
@@ -510,4 +816,9 @@ public abstract class UChannel
|
|||||||
{
|
{
|
||||||
return !connection.IsInternalAck() && bunch != null && bunch.GetNumBytes() > NetMaxConstructedPartialBunchSizeBytes;
|
return !connection.IsInternalAck() && bunch != null && bunch.GetNumBytes() > NetMaxConstructedPartialBunchSizeBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsBunchTooLarge(UNetConnection connection, FOutBunch? bunch)
|
||||||
|
{
|
||||||
|
return !connection.IsInternalAck() && bunch != null && bunch.GetNumBytes() > NetMaxConstructedPartialBunchSizeBytes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
using Prospect.Unreal.Core.Names;
|
|
||||||
using Prospect.Unreal.Net.Packets.Bunch;
|
|
||||||
|
|
||||||
namespace Prospect.Unreal.Net.Channels;
|
|
||||||
|
|
||||||
public class UControlChannel : UChannel
|
|
||||||
{
|
|
||||||
public UControlChannel()
|
|
||||||
{
|
|
||||||
ChType = EChannelType.CHTYPE_Control;
|
|
||||||
ChName = UnrealNames.FNames[UnrealNameKey.Control];
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void ReceivedBunch(FInBunch bunch)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
using Prospect.Unreal.Core.Names;
|
using Prospect.Unreal.Core.Names;
|
||||||
using Prospect.Unreal.Net.Packets.Bunch;
|
using Prospect.Unreal.Net.Packets.Bunch;
|
||||||
|
|
||||||
namespace Prospect.Unreal.Net.Channels;
|
namespace Prospect.Unreal.Net.Channels.Voice;
|
||||||
|
|
||||||
public class UVoiceChannel : UChannel
|
public class UVoiceChannel : UChannel
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Prospect.Unreal.Net;
|
||||||
|
|
||||||
|
public enum EResendAllDataState
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
SinceOpen,
|
||||||
|
SinceCheckpoint
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Prospect.Unreal.Net;
|
||||||
|
|
||||||
|
public enum EWriteBitsDataType
|
||||||
|
{
|
||||||
|
Unknown,
|
||||||
|
Bunch,
|
||||||
|
Ack
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Prospect.Unreal.Net;
|
||||||
|
|
||||||
|
public class FGuid
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Prospect.Unreal.Net;
|
||||||
|
|
||||||
|
public class FNetworkGUID
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
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;
|
||||||
|
|
||||||
namespace Prospect.Unreal.Net;
|
namespace Prospect.Unreal.Net;
|
||||||
|
|
||||||
@@ -24,5 +25,5 @@ public interface FNetworkNotify
|
|||||||
///
|
///
|
||||||
/// (i.e. use FNetControlMessage::Receive())
|
/// (i.e. use FNetControlMessage::Receive())
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void NotifyControlMessage(UNetConnection connection, byte messageType, FInBunch bunch);
|
void NotifyControlMessage(UNetConnection connection, NMT messageType, FInBunch bunch);
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,9 @@
|
|||||||
|
|
||||||
public class FOutPacketTraits
|
public class FOutPacketTraits
|
||||||
{
|
{
|
||||||
|
public bool AllowCompression { get; set; } = true;
|
||||||
|
public uint NumAckBits { get; set; }
|
||||||
|
public uint NumBunchBits { get; set; }
|
||||||
|
public bool IsKeepAlive { get; set; }
|
||||||
|
public bool IsCompressed { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,29 @@
|
|||||||
namespace Prospect.Unreal.Net;
|
using Prospect.Unreal.Core.Names;
|
||||||
|
|
||||||
public class FPacketIdRange
|
namespace Prospect.Unreal.Net;
|
||||||
|
|
||||||
|
public readonly struct FPacketIdRange
|
||||||
{
|
{
|
||||||
public int First = -1;
|
public FPacketIdRange()
|
||||||
public int Last = -1;
|
{
|
||||||
|
First = UnrealConstants.IndexNone;
|
||||||
|
Last = UnrealConstants.IndexNone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FPacketIdRange(int packetId)
|
||||||
|
{
|
||||||
|
First = packetId;
|
||||||
|
Last = packetId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FPacketIdRange(int first, int last)
|
||||||
|
{
|
||||||
|
First = first;
|
||||||
|
Last = last;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int First { get; }
|
||||||
|
public int Last { get; }
|
||||||
|
|
||||||
public bool InRange(int packetId)
|
public bool InRange(int packetId)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
namespace Prospect.Unreal.Net;
|
||||||
|
|
||||||
|
public class FUniqueNetIdRepl
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -5,8 +5,6 @@ namespace Prospect.Unreal.Net;
|
|||||||
|
|
||||||
public abstract class HandlerComponent
|
public abstract class HandlerComponent
|
||||||
{
|
{
|
||||||
private bool _bRequiresHandshake;
|
|
||||||
private bool _bRequiresReliability;
|
|
||||||
private bool _bActive;
|
private bool _bActive;
|
||||||
private bool _bInitialized;
|
private bool _bInitialized;
|
||||||
private string _name;
|
private string _name;
|
||||||
@@ -14,8 +12,8 @@ public abstract class HandlerComponent
|
|||||||
protected HandlerComponent(PacketHandler handler, string name)
|
protected HandlerComponent(PacketHandler handler, string name)
|
||||||
{
|
{
|
||||||
_name = name;
|
_name = name;
|
||||||
_bRequiresHandshake = false;
|
RequiresHandshake = false;
|
||||||
_bRequiresReliability = false;
|
RequiresReliability = false;
|
||||||
_bActive = false;
|
_bActive = false;
|
||||||
_bInitialized = false;
|
_bInitialized = false;
|
||||||
|
|
||||||
@@ -26,6 +24,14 @@ public abstract class HandlerComponent
|
|||||||
protected PacketHandler Handler { get; }
|
protected PacketHandler Handler { get; }
|
||||||
protected HandlerComponentState State { get; private set; }
|
protected HandlerComponentState State { get; private set; }
|
||||||
|
|
||||||
|
public bool RequiresHandshake { get; protected set; }
|
||||||
|
public bool RequiresReliability { get; protected set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum number of Outgoing packet bits supported (automatically calculated to factor in other HandlerComponent reserved bits)
|
||||||
|
/// </summary>
|
||||||
|
public uint MaxOutgoingBits { get; set; }
|
||||||
|
|
||||||
public virtual bool IsActive()
|
public virtual bool IsActive()
|
||||||
{
|
{
|
||||||
return _bActive;
|
return _bActive;
|
||||||
@@ -41,21 +47,11 @@ public abstract class HandlerComponent
|
|||||||
return _bInitialized;
|
return _bInitialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool RequiresHandshake()
|
|
||||||
{
|
|
||||||
return _bRequiresHandshake;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool RequiresReliability()
|
|
||||||
{
|
|
||||||
return _bRequiresReliability;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void Incoming(FBitReader packet)
|
public virtual void Incoming(FBitReader packet)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void Outgoing(FBitWriter packet, FOutPacketTraits traits)
|
public virtual void Outgoing(ref FBitWriter packet, FOutPacketTraits traits)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,5 +100,6 @@ public abstract class HandlerComponent
|
|||||||
protected void Initialized()
|
protected void Initialized()
|
||||||
{
|
{
|
||||||
_bInitialized = true;
|
_bInitialized = true;
|
||||||
|
Handler.HandlerComponentInitialized(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
|
using Prospect.Unreal.Exceptions;
|
||||||
using Prospect.Unreal.Serialization;
|
using Prospect.Unreal.Serialization;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
@@ -11,30 +12,38 @@ public record ProcessedPacket(byte[] Data, int CountBits, bool Error = false);
|
|||||||
public class PacketHandler
|
public class PacketHandler
|
||||||
{
|
{
|
||||||
private static readonly ILogger Logger = Log.ForContext<PacketHandler>();
|
private static readonly ILogger Logger = Log.ForContext<PacketHandler>();
|
||||||
|
private static readonly IPEndPoint EmptyAddress = new IPEndPoint(IPAddress.None, 0);
|
||||||
|
|
||||||
private readonly List<HandlerComponent> _handlerComponents;
|
private readonly List<HandlerComponent> _handlerComponents;
|
||||||
|
|
||||||
private bool _bConnectionlessHandler;
|
private bool _bConnectionlessHandler;
|
||||||
private bool _bRawSend;
|
private bool _bRawSend;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum supported packet size (reflects UNetConnection::MaxPacket)
|
||||||
|
/// </summary>
|
||||||
|
private uint _maxPacketBits;
|
||||||
|
|
||||||
private HandlerState _state;
|
private HandlerState _state;
|
||||||
private ReliabilityHandlerComponent? _reliabilityComponent;
|
private ReliabilityHandlerComponent? _reliabilityComponent;
|
||||||
|
|
||||||
private FBitWriter _outgoingPacket;
|
private FBitWriter _outgoingPacket;
|
||||||
private FBitReader _incomingPacket;
|
private FBitReader _incomingPacket;
|
||||||
|
|
||||||
|
private bool _bBeganHandshaking;
|
||||||
|
|
||||||
public PacketHandler()
|
public PacketHandler()
|
||||||
{
|
{
|
||||||
_bConnectionlessHandler = false;
|
_bConnectionlessHandler = false;
|
||||||
_state = HandlerState.Uninitialized;
|
_state = HandlerState.Uninitialized;
|
||||||
_handlerComponents = new List<HandlerComponent>();
|
_handlerComponents = new List<HandlerComponent>();
|
||||||
_reliabilityComponent = null;
|
_reliabilityComponent = null;
|
||||||
_outgoingPacket = new FBitWriter(0);
|
_outgoingPacket = new FBitWriter(0, true, false);
|
||||||
|
_outgoingPacket.AllowAppend(1);
|
||||||
_incomingPacket = new FBitReader(Array.Empty<byte>());
|
_incomingPacket = new FBitReader(Array.Empty<byte>());
|
||||||
|
|
||||||
Mode = HandlerMode.Server;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public HandlerMode Mode { get; }
|
public HandlerMode Mode { get; private set; }
|
||||||
|
|
||||||
public void Tick(float deltaTime)
|
public void Tick(float deltaTime)
|
||||||
{
|
{
|
||||||
@@ -44,8 +53,10 @@ public class PacketHandler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Initialize(bool bConnectionlessOnly)
|
public void Initialize(HandlerMode mode, uint inMaxPacketBits, bool bConnectionlessOnly)
|
||||||
{
|
{
|
||||||
|
Mode = mode;
|
||||||
|
_maxPacketBits = inMaxPacketBits;
|
||||||
_bConnectionlessHandler = bConnectionlessOnly;
|
_bConnectionlessHandler = bConnectionlessOnly;
|
||||||
|
|
||||||
if (!_bConnectionlessHandler)
|
if (!_bConnectionlessHandler)
|
||||||
@@ -83,6 +94,9 @@ public class PacketHandler
|
|||||||
component.Initialize();
|
component.Initialize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called early, to ensure that all handlers report a valid reserved packet bits value (triggers an assert if not)
|
||||||
|
GetTotalReservedPacketBits();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IncomingConnectionless(FReceivedPacketView packetView)
|
public bool IncomingConnectionless(FReceivedPacketView packetView)
|
||||||
@@ -97,13 +111,18 @@ public class PacketHandler
|
|||||||
return Outgoing_Internal(packet, countBits, traits, true, address);
|
return Outgoing_Internal(packet, countBits, traits, true, address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ProcessedPacket Outgoing(byte[] packet, int countBits, FOutPacketTraits traits)
|
||||||
|
{
|
||||||
|
return Outgoing_Internal(packet, countBits, traits, false, EmptyAddress);
|
||||||
|
}
|
||||||
|
|
||||||
public void BeginHandshaking()
|
public void BeginHandshaking()
|
||||||
{
|
{
|
||||||
// bBeganHandshaking = true;
|
// bBeganHandshaking = true;
|
||||||
|
|
||||||
foreach (var component in _handlerComponents)
|
foreach (var component in _handlerComponents)
|
||||||
{
|
{
|
||||||
if (component.RequiresHandshake() && !component.IsInitialized())
|
if (component.RequiresHandshake && !component.IsInitialized())
|
||||||
{
|
{
|
||||||
component.NotifiyHandshakeBegin();
|
component.NotifiyHandshakeBegin();
|
||||||
}
|
}
|
||||||
@@ -129,6 +148,11 @@ public class PacketHandler
|
|||||||
// NO-OP
|
// NO-OP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OutgoingHigh(FBitWriter writer)
|
||||||
|
{
|
||||||
|
// NO-OP
|
||||||
|
}
|
||||||
|
|
||||||
private bool Incoming_Internal(FReceivedPacketView packetView)
|
private bool Incoming_Internal(FReceivedPacketView packetView)
|
||||||
{
|
{
|
||||||
var returnVal = true;
|
var returnVal = true;
|
||||||
@@ -201,13 +225,69 @@ public class PacketHandler
|
|||||||
{
|
{
|
||||||
if (!_bRawSend)
|
if (!_bRawSend)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
_outgoingPacket.Reset();
|
||||||
|
|
||||||
|
if (_state == HandlerState.Uninitialized)
|
||||||
|
{
|
||||||
|
UpdateInitialState();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_state == HandlerState.Initialized)
|
||||||
|
{
|
||||||
|
_outgoingPacket.SerializeBits(packet, countBits);
|
||||||
|
|
||||||
|
foreach (var component in _handlerComponents)
|
||||||
|
{
|
||||||
|
if (component.IsActive())
|
||||||
|
{
|
||||||
|
if (_outgoingPacket.GetNumBits() <= component.MaxOutgoingBits)
|
||||||
|
{
|
||||||
|
if (bConnectionLess)
|
||||||
|
{
|
||||||
|
component.OutgoingConnectionless(address, _outgoingPacket, traits);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return new ProcessedPacket(packet, countBits);
|
component.Outgoing(ref _outgoingPacket, traits);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_outgoingPacket.SetError();
|
||||||
|
Logger.Error("Packet exceeded HandlerComponents 'MaxOutgoingBits' value: {A} vs {B}", _outgoingPacket.GetNumBits(), component.MaxOutgoingBits);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a termination bit, the same as the UNetConnection code does, if appropriate
|
||||||
|
if (_handlerComponents.Count > 0 && _outgoingPacket.GetNumBits() > 0)
|
||||||
|
{
|
||||||
|
_outgoingPacket.WriteBit(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bConnectionLess && _reliabilityComponent != null && _outgoingPacket.GetNumBits() > 0)
|
||||||
|
{
|
||||||
|
// Let the reliability handler know about all processed packets, so it can record them for resending if needed
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Buffer any packets being sent from game code until processors are initialized
|
||||||
|
else if (_state == HandlerState.InitializingComponents && countBits > 0)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_outgoingPacket.IsError())
|
||||||
|
{
|
||||||
|
return new ProcessedPacket(_outgoingPacket.GetData(), (int)_outgoingPacket.GetNumBits());
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ProcessedPacket(packet, countBits, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ProcessedPacket(packet, countBits);
|
||||||
|
}
|
||||||
|
|
||||||
private void SetState(HandlerState state)
|
private void SetState(HandlerState state)
|
||||||
{
|
{
|
||||||
@@ -289,4 +369,76 @@ public class PacketHandler
|
|||||||
{
|
{
|
||||||
return _bRawSend;
|
return _bRawSend;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int GetTotalReservedPacketBits()
|
||||||
|
{
|
||||||
|
var returnVal = 0;
|
||||||
|
var curMaxOutgoingBits = _maxPacketBits;
|
||||||
|
|
||||||
|
for (int i = _handlerComponents.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var curComponent = _handlerComponents[i];
|
||||||
|
var curReservedBits = curComponent.GetReservedPacketBits();
|
||||||
|
|
||||||
|
// Specifying the reserved packet bits is mandatory, even if zero (as accidentally forgetting, leads to hard to trace issues).
|
||||||
|
if (curReservedBits == -1)
|
||||||
|
{
|
||||||
|
throw new UnrealNetException("Handler returned invalid 'GetReservedPacketBits' value");
|
||||||
|
}
|
||||||
|
|
||||||
|
curComponent.MaxOutgoingBits = curMaxOutgoingBits;
|
||||||
|
curMaxOutgoingBits -= (uint) curReservedBits;
|
||||||
|
|
||||||
|
returnVal += curReservedBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reserve space for the termination bit
|
||||||
|
if (_handlerComponents.Count > 0)
|
||||||
|
{
|
||||||
|
returnVal++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HandlerComponentInitialized(HandlerComponent inComponent)
|
||||||
|
{
|
||||||
|
// Check if all handlers are initialized
|
||||||
|
if (_state != HandlerState.Initialized)
|
||||||
|
{
|
||||||
|
var bAllInitialized = true;
|
||||||
|
var bEncounteredComponent = false;
|
||||||
|
var bPassedHandshakeNotify = false;
|
||||||
|
|
||||||
|
for (int i = _handlerComponents.Count - 1; i >= 0; --i)
|
||||||
|
{
|
||||||
|
var curComponent = _handlerComponents[i];
|
||||||
|
|
||||||
|
if (!curComponent.IsInitialized())
|
||||||
|
{
|
||||||
|
bAllInitialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bEncounteredComponent)
|
||||||
|
{
|
||||||
|
// If the initialized component required a handshake, pass on notification to the next handshaking component
|
||||||
|
// (components closer to the Socket, perform their handshake first)
|
||||||
|
if (_bBeganHandshaking && !curComponent.IsInitialized() && inComponent.RequiresHandshake && !bPassedHandshakeNotify && curComponent.RequiresHandshake)
|
||||||
|
{
|
||||||
|
curComponent.NotifiyHandshakeBegin();
|
||||||
|
bPassedHandshakeNotify = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bEncounteredComponent = curComponent == inComponent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bAllInitialized)
|
||||||
|
{
|
||||||
|
HandlerInitialized();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using Prospect.Unreal.Net.Channels;
|
||||||
|
|
||||||
|
namespace Prospect.Unreal.Net.Packets.Bunch;
|
||||||
|
|
||||||
|
public class FControlChannelOutBunch : FOutBunch
|
||||||
|
{
|
||||||
|
public FControlChannelOutBunch(UChannel inChannel, bool bClose) : base(inChannel, bClose)
|
||||||
|
{
|
||||||
|
bReliable = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
using Prospect.Unreal.Core.Names;
|
||||||
|
using Prospect.Unreal.Net.Channels;
|
||||||
|
using Prospect.Unreal.Serialization;
|
||||||
|
|
||||||
|
namespace Prospect.Unreal.Net.Packets.Bunch;
|
||||||
|
|
||||||
|
public class FOutBunch : FNetBitWriter
|
||||||
|
{
|
||||||
|
public FOutBunch() : base(0)
|
||||||
|
{
|
||||||
|
ChName = UnrealNames.FNames[UnrealNameKey.None];
|
||||||
|
}
|
||||||
|
|
||||||
|
public FOutBunch(UChannel inChannel, bool bInClose) : base(
|
||||||
|
inChannel.Connection!.PackageMap!,
|
||||||
|
inChannel.Connection.GetMaxSingleBunchSizeBits())
|
||||||
|
{
|
||||||
|
Next = null;
|
||||||
|
Channel = inChannel;
|
||||||
|
Time = 0;
|
||||||
|
ChIndex = inChannel.ChIndex;
|
||||||
|
ChType = inChannel.ChType;
|
||||||
|
ChName = inChannel.ChName;
|
||||||
|
ChSequence = 0;
|
||||||
|
PacketId = 0;
|
||||||
|
ReceivedAck = false;
|
||||||
|
bOpen = false;
|
||||||
|
bClose = bInClose;
|
||||||
|
bDormant = false;
|
||||||
|
bIsReplicationPaused = false;
|
||||||
|
bReliable = false;
|
||||||
|
bPartial = false;
|
||||||
|
bPartialInitial = false;
|
||||||
|
bPartialFinal = false;
|
||||||
|
bHasPackageMapExports = false;
|
||||||
|
bHasMustBeMappedGUIDs = false;
|
||||||
|
CloseReason = EChannelCloseReason.Destroyed;
|
||||||
|
|
||||||
|
// Match the byte swapping settings of the connection
|
||||||
|
// TODO: SetByteSwapping(Channel->Connection->bNeedsByteSwapping);
|
||||||
|
|
||||||
|
// Reserve channel and set bunch info.
|
||||||
|
if (Channel.NumOutRec >= UNetConnection.ReliableBuffer - 1 + (bClose ? 1 : 0))
|
||||||
|
{
|
||||||
|
SetOverflowed(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FOutBunch(UPackageMap inPackageMap, long maxBits) : base(inPackageMap, maxBits)
|
||||||
|
{
|
||||||
|
Next = null;
|
||||||
|
Channel = null;
|
||||||
|
Time = 0;
|
||||||
|
ChIndex = 0;
|
||||||
|
ChType = EChannelType.CHTYPE_None;
|
||||||
|
ChName = UnrealNames.FNames[UnrealNameKey.None];
|
||||||
|
ChSequence = 0;
|
||||||
|
PacketId = 0;
|
||||||
|
ReceivedAck = false;
|
||||||
|
bOpen = false;
|
||||||
|
bClose = false;
|
||||||
|
bDormant = false;
|
||||||
|
bIsReplicationPaused = false;
|
||||||
|
bReliable = false;
|
||||||
|
bPartial = false;
|
||||||
|
bPartialInitial = false;
|
||||||
|
bPartialFinal = false;
|
||||||
|
bHasPackageMapExports = false;
|
||||||
|
bHasMustBeMappedGUIDs = false;
|
||||||
|
CloseReason = EChannelCloseReason.Destroyed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FOutBunch(FOutBunch bunch) : base(bunch)
|
||||||
|
{
|
||||||
|
Next = bunch.Next;
|
||||||
|
Channel = bunch.Channel;
|
||||||
|
Time = bunch.Time;
|
||||||
|
ChIndex = bunch.ChIndex;
|
||||||
|
ChType = bunch.ChType;
|
||||||
|
ChName = bunch.ChName;
|
||||||
|
ChSequence = bunch.ChSequence;
|
||||||
|
PacketId = bunch.PacketId;
|
||||||
|
ReceivedAck = bunch.ReceivedAck;
|
||||||
|
bOpen = bunch.bOpen;
|
||||||
|
bClose = bunch.bClose;
|
||||||
|
bDormant = bunch.bDormant;
|
||||||
|
bIsReplicationPaused = bunch.bIsReplicationPaused;
|
||||||
|
bReliable = bunch.bReliable;
|
||||||
|
bPartial = bunch.bPartial;
|
||||||
|
bPartialInitial = bunch.bPartialInitial;
|
||||||
|
bPartialFinal = bunch.bPartialFinal;
|
||||||
|
bHasPackageMapExports = bunch.bHasPackageMapExports;
|
||||||
|
bHasMustBeMappedGUIDs = bunch.bHasMustBeMappedGUIDs;
|
||||||
|
CloseReason = bunch.CloseReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FOutBunch? Next { get; set; }
|
||||||
|
|
||||||
|
public UChannel? Channel { get; set; }
|
||||||
|
|
||||||
|
public double Time { get; set; }
|
||||||
|
|
||||||
|
public int ChIndex { get; set; }
|
||||||
|
|
||||||
|
public EChannelType ChType { get; set; }
|
||||||
|
|
||||||
|
public FName ChName { get; set; }
|
||||||
|
|
||||||
|
public int ChSequence { get; set; }
|
||||||
|
|
||||||
|
public int PacketId { get; set; }
|
||||||
|
|
||||||
|
public bool ReceivedAck { 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 EChannelCloseReason CloseReason { get; set; }
|
||||||
|
|
||||||
|
public List<FNetworkGUID> ExportNetGUIDs { get; } = new List<FNetworkGUID>();
|
||||||
|
|
||||||
|
public List<ulong> NetFieldExports { get; } = new List<ulong>();
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
namespace Prospect.Unreal.Net.Packets.Control;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
|
||||||
|
internal class NetControlMessageAttribute : Attribute
|
||||||
|
{
|
||||||
|
public NetControlMessageAttribute(string name, int index, params Type[] args)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Index = index;
|
||||||
|
Args = args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name { get; }
|
||||||
|
public int Index { get; }
|
||||||
|
public Type[] Args { get; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using Prospect.Unreal.Core;
|
||||||
|
using Prospect.Unreal.Net;
|
||||||
|
using Prospect.Unreal.Net.Packets.Control;
|
||||||
|
|
||||||
|
[assembly: NetControlMessage("Hello", 0, typeof(byte), typeof(uint), typeof(FString))]
|
||||||
|
[assembly: NetControlMessage("Welcome", 1, typeof(FString), typeof(FString), typeof(FString))]
|
||||||
|
[assembly: NetControlMessage("Upgrade", 2, typeof(uint))]
|
||||||
|
[assembly: NetControlMessage("Challenge", 3, typeof(FString))]
|
||||||
|
[assembly: NetControlMessage("Netspeed", 4, typeof(int))]
|
||||||
|
[assembly: NetControlMessage("Login", 5, typeof(FString), typeof(FString), typeof(FUniqueNetIdRepl), typeof(FString))]
|
||||||
|
[assembly: NetControlMessage("Failure", 6, typeof(FString))]
|
||||||
|
[assembly: NetControlMessage("Join", 9)]
|
||||||
|
[assembly: NetControlMessage("JoinSplit", 10, typeof(FString), typeof(FUniqueNetIdRepl))]
|
||||||
|
[assembly: NetControlMessage("Skip", 12, typeof(FGuid))]
|
||||||
|
[assembly: NetControlMessage("Abort", 13, typeof(FGuid))]
|
||||||
|
[assembly: NetControlMessage("PCSwap", 15, typeof(int))]
|
||||||
|
[assembly: NetControlMessage("ActorChannelFailure", 16, typeof(int))]
|
||||||
|
[assembly: NetControlMessage("DebugText", 17, typeof(FString))]
|
||||||
|
[assembly: NetControlMessage("NetGUIDAssign", 18, typeof(FNetworkGUID), typeof(FString))]
|
||||||
|
[assembly: NetControlMessage("SecurityViolation", 19, typeof(FString))]
|
||||||
|
[assembly: NetControlMessage("GameSpecific", 20, typeof(byte), typeof(FString))]
|
||||||
|
[assembly: NetControlMessage("EncryptionAck", 21)]
|
||||||
|
[assembly: NetControlMessage("DestructionInfo", 22)]
|
||||||
|
|
||||||
|
[assembly: NetControlMessage("BeaconWelcome", 25)]
|
||||||
|
[assembly: NetControlMessage("BeaconJoin", 26, typeof(FString), typeof(FUniqueNetIdRepl))]
|
||||||
|
[assembly: NetControlMessage("BeaconAssignGUID", 27, typeof(FNetworkGUID))]
|
||||||
|
[assembly: NetControlMessage("BeaconNetGUIDAck", 28, typeof(FString))]
|
||||||
@@ -14,6 +14,8 @@ public class FNetPacketNotify
|
|||||||
public const int MaxSequenceHistoryLength = 256;
|
public const int MaxSequenceHistoryLength = 256;
|
||||||
|
|
||||||
private readonly Queue<FSentAckData> _ackRecord = new Queue<FSentAckData>();
|
private readonly Queue<FSentAckData> _ackRecord = new Queue<FSentAckData>();
|
||||||
|
private uint _writtenHistoryWordCount;
|
||||||
|
private SequenceNumber _writtenInAckSeq;
|
||||||
|
|
||||||
private readonly SequenceHistory _inSeqHistory = new SequenceHistory();
|
private readonly SequenceHistory _inSeqHistory = new SequenceHistory();
|
||||||
private SequenceNumber _inSeq;
|
private SequenceNumber _inSeq;
|
||||||
@@ -33,6 +35,71 @@ public class FNetPacketNotify
|
|||||||
_outAckSeq = new SequenceNumber((ushort)(initialOutSeq.Value - 1));
|
_outAckSeq = new SequenceNumber((ushort)(initialOutSeq.Value - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SequenceNumber GetInSeq()
|
||||||
|
{
|
||||||
|
return _inSeq;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SequenceNumber GetInAckSeq()
|
||||||
|
{
|
||||||
|
return _inAckSeq;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SequenceNumber GetOutSeq()
|
||||||
|
{
|
||||||
|
return _outSeq;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SequenceNumber GetOutAckSeq()
|
||||||
|
{
|
||||||
|
return _outAckSeq;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// These methods must always write and read the exact same number of bits, that is the reason for not using WriteInt/WrittedWrappedInt
|
||||||
|
/// </summary>
|
||||||
|
public bool WriteHeader(FBitWriter writer, bool bRefresh)
|
||||||
|
{
|
||||||
|
// we always write at least 1 word
|
||||||
|
var currentHistoryWorkCount = Math.Clamp((GetCurrentSequenceHistoryLength() + SequenceHistory.BitsPerWord - 1) / SequenceHistory.BitsPerWord, 1, SequenceHistory.WordCount);
|
||||||
|
|
||||||
|
// We can only do a refresh if we do not need more space for the history
|
||||||
|
if (bRefresh && (currentHistoryWorkCount > _writtenHistoryWordCount))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// How many words of ack data should we write? If this is a refresh we must write the same size as the original header
|
||||||
|
_writtenHistoryWordCount = bRefresh ? _writtenHistoryWordCount : currentHistoryWorkCount;
|
||||||
|
|
||||||
|
// This is the last InAck we have acknowledged at this time
|
||||||
|
_writtenInAckSeq = _inAckSeq;
|
||||||
|
|
||||||
|
var seq = _outSeq;
|
||||||
|
var ackedSeq = _inAckSeq;
|
||||||
|
|
||||||
|
// Pack data into a uint
|
||||||
|
var packedHeader = FPackedHeader.Pack(seq, ackedSeq, _writtenHistoryWordCount - 1);
|
||||||
|
|
||||||
|
// Write packed header
|
||||||
|
writer.WriteUInt32(packedHeader);
|
||||||
|
|
||||||
|
// Write ack history
|
||||||
|
_inSeqHistory.Write(writer, _writtenHistoryWordCount);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint GetCurrentSequenceHistoryLength()
|
||||||
|
{
|
||||||
|
if (_inAckSeq.GreaterEq(_inAckSeqAck))
|
||||||
|
{
|
||||||
|
return (uint) SequenceNumber.Diff(_inAckSeq, _inAckSeqAck);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SequenceHistory.Size;
|
||||||
|
}
|
||||||
|
|
||||||
public bool ReadHeader(ref FNotificationHeader data, FBitReader reader)
|
public bool ReadHeader(ref FNotificationHeader data, FBitReader reader)
|
||||||
{
|
{
|
||||||
var packedHeader = reader.ReadUInt32();
|
var packedHeader = reader.ReadUInt32();
|
||||||
@@ -173,4 +240,13 @@ public class FNetPacketNotify
|
|||||||
_inSeqHistory.AddDeliveryStatus(bReportAcked);
|
_inSeqHistory.AddDeliveryStatus(bReportAcked);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SequenceNumber CommitAndIncrementOutSeq()
|
||||||
|
{
|
||||||
|
// Add entry to the ack-record so that we can update the InAckSeqAck when we received the ack for this OutSeq.
|
||||||
|
_ackRecord.Enqueue(new FSentAckData(_outSeq, _writtenInAckSeq));
|
||||||
|
_writtenHistoryWordCount = 0;
|
||||||
|
|
||||||
|
return _outSeq.IncrementAndGet();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -10,6 +10,17 @@ public class FPackedHeader
|
|||||||
public const int AckSeqShift = HistoryWordCountBits;
|
public const int AckSeqShift = HistoryWordCountBits;
|
||||||
public const int SeqShift = AckSeqShift + FNetPacketNotify.SequenceNumberBits;
|
public const int SeqShift = AckSeqShift + FNetPacketNotify.SequenceNumberBits;
|
||||||
|
|
||||||
|
public static uint Pack(SequenceNumber seq, SequenceNumber ackedSeq, uint historyWordCount)
|
||||||
|
{
|
||||||
|
uint packed = 0;
|
||||||
|
|
||||||
|
packed |= (uint)(seq.Value << SeqShift);
|
||||||
|
packed |= (uint)(ackedSeq.Value << AckSeqShift);
|
||||||
|
packed |= (uint)(historyWordCount & HistoryWordCountMask);
|
||||||
|
|
||||||
|
return packed;
|
||||||
|
}
|
||||||
|
|
||||||
public static SequenceNumber GetSeq(uint packed)
|
public static SequenceNumber GetSeq(uint packed)
|
||||||
{
|
{
|
||||||
return new SequenceNumber((ushort)(packed >> SeqShift & SeqMask));
|
return new SequenceNumber((ushort)(packed >> SeqShift & SeqMask));
|
||||||
|
|||||||
@@ -57,4 +57,13 @@ public readonly struct SequenceHistory
|
|||||||
_storage[i] = reader.ReadUInt32();
|
_storage[i] = reader.ReadUInt32();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Write(FBitWriter writer, uint numWords)
|
||||||
|
{
|
||||||
|
numWords = Math.Min(numWords, WordCount);
|
||||||
|
for (var i = 0; i < numWords; i++)
|
||||||
|
{
|
||||||
|
writer.WriteUInt32(_storage[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -76,6 +76,10 @@ public class StatelessConnectHandlerComponent : HandlerComponent
|
|||||||
|
|
||||||
public StatelessConnectHandlerComponent(PacketHandler handler) : base(handler, nameof(StatelessConnectHandlerComponent))
|
public StatelessConnectHandlerComponent(PacketHandler handler) : base(handler, nameof(StatelessConnectHandlerComponent))
|
||||||
{
|
{
|
||||||
|
SetActive(true);
|
||||||
|
|
||||||
|
RequiresHandshake = true;
|
||||||
|
|
||||||
_handshakeSecret = new byte[2][];
|
_handshakeSecret = new byte[2][];
|
||||||
_activeSecret = byte.MaxValue;
|
_activeSecret = byte.MaxValue;
|
||||||
_lastChallengeSuccessAddress = null;
|
_lastChallengeSuccessAddress = null;
|
||||||
@@ -202,9 +206,21 @@ public class StatelessConnectHandlerComponent : HandlerComponent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Outgoing(FBitWriter packet, FOutPacketTraits traits)
|
public override void Outgoing(ref FBitWriter packet, FOutPacketTraits traits)
|
||||||
{
|
{
|
||||||
base.Outgoing(packet, traits);
|
const bool bHandshakePacket = false;
|
||||||
|
|
||||||
|
var newPacket = new FBitWriter(GetAdjustedSizeBits((int)packet.GetNumBits()) + 1, true, false);
|
||||||
|
|
||||||
|
if (_magicHeader.Length > 0)
|
||||||
|
{
|
||||||
|
newPacket.SerializeBits(_magicHeader, _magicHeader.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
newPacket.WriteBit(bHandshakePacket);
|
||||||
|
newPacket.SerializeBits(packet.GetData(), packet.GetNumBits());
|
||||||
|
|
||||||
|
packet = newPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void IncomingConnectionless(FIncomingPacketRef packetRef)
|
public override void IncomingConnectionless(FIncomingPacketRef packetRef)
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
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.Exceptions;
|
||||||
|
using Prospect.Unreal.Net.Packets.Control;
|
||||||
|
|
||||||
namespace Prospect.Unreal.Net;
|
namespace Prospect.Unreal.Net;
|
||||||
|
|
||||||
@@ -39,8 +41,12 @@ public class UIpConnection : UNetConnection
|
|||||||
RemoteAddr = inRemoteAddr;
|
RemoteAddr = inRemoteAddr;
|
||||||
Url.Host = RemoteAddr.Address;
|
Url.Host = RemoteAddr.Address;
|
||||||
|
|
||||||
|
// Initialize our send bunch
|
||||||
|
InitSendBuffer();
|
||||||
|
|
||||||
|
// This is for a client that needs to log in, setup ClientLoginState and ExpectedClientLoginMsgType to reflect that
|
||||||
SetClientLoginState(EClientLoginState.LoggingIn);
|
SetClientLoginState(EClientLoginState.LoggingIn);
|
||||||
SetExpectedClientLoginMsgType(0); // TODO: NMT_HELLO
|
SetExpectedClientLoginMsgType(NMT.Hello); // 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)
|
||||||
@@ -50,7 +56,38 @@ public class UIpConnection : UNetConnection
|
|||||||
|
|
||||||
public override void LowLevelSend(byte[] data, int countBits, FOutPacketTraits traits)
|
public override void LowLevelSend(byte[] data, int countBits, FOutPacketTraits traits)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
// TODO: Improve according to UE4
|
||||||
|
|
||||||
|
var dataToSend = data;
|
||||||
|
|
||||||
|
// 243
|
||||||
|
// 244
|
||||||
|
|
||||||
|
// Process any packet modifiers
|
||||||
|
if (Handler != null && !Handler.GetRawSend())
|
||||||
|
{
|
||||||
|
var processedData = Handler.Outgoing(data, countBits, traits);
|
||||||
|
if (!processedData.Error)
|
||||||
|
{
|
||||||
|
dataToSend = processedData.Data;
|
||||||
|
countBits = processedData.CountBits;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
countBits = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var countBytes = FMath.DivideAndRoundUp(countBits, 8);
|
||||||
|
if (countBits > 0)
|
||||||
|
{
|
||||||
|
if (Socket == null)
|
||||||
|
{
|
||||||
|
throw new UnrealNetException();
|
||||||
|
}
|
||||||
|
|
||||||
|
Socket.Send(dataToSend, countBytes, RemoteAddr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string LowLevelGetRemoteAddress(bool bAppendPort = false)
|
public override string LowLevelGetRemoteAddress(bool bAppendPort = false)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using Prospect.Unreal.Core.Names;
|
|||||||
using Prospect.Unreal.Exceptions;
|
using Prospect.Unreal.Exceptions;
|
||||||
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.Header;
|
using Prospect.Unreal.Net.Packets.Header;
|
||||||
using Prospect.Unreal.Net.Packets.Header.Sequence;
|
using Prospect.Unreal.Net.Packets.Header.Sequence;
|
||||||
using Prospect.Unreal.Net.Player;
|
using Prospect.Unreal.Net.Player;
|
||||||
@@ -23,11 +24,16 @@ public abstract class UNetConnection : UPlayer
|
|||||||
public const int MaxChSequence = 1024;
|
public const int MaxChSequence = 1024;
|
||||||
public const int MaxBunchHeaderBits = 256;
|
public const int MaxBunchHeaderBits = 256;
|
||||||
public const int MaxPacketSize = 1024;
|
public const int MaxPacketSize = 1024;
|
||||||
|
public const int MaxPacketReliableSequenceHeaderBits = 32 + FNetPacketNotify.MaxSequenceHistoryLength;
|
||||||
|
public const int MaxPacketInfoHeaderBits = 1 /* bHasPacketInfo */ + NumBitsForJitterClockTimeInHeader + 1 /* bHasServerFrameTime */ + 8 /* ServerFrameTime */;
|
||||||
|
public const int MaxPacketHeaderBits = MaxPacketReliableSequenceHeaderBits + MaxPacketInfoHeaderBits;
|
||||||
|
public const int MaxPacketTrailerBits = 1;
|
||||||
|
|
||||||
public const int DefaultMaxChannelSize = 32767;
|
public const int DefaultMaxChannelSize = 32767;
|
||||||
|
|
||||||
public const int MaxJitterClockTimeValue = 1023;
|
|
||||||
public const int NumBitsForJitterClockTimeInHeader = 10;
|
public const int NumBitsForJitterClockTimeInHeader = 10;
|
||||||
|
public const int MaxJitterClockTimeValue = (1 << NumBitsForJitterClockTimeInHeader) - 1;
|
||||||
|
public const int MaxJitterPrecisionInMS = 1000;
|
||||||
|
|
||||||
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;
|
||||||
@@ -49,6 +55,24 @@ public abstract class UNetConnection : UPlayer
|
|||||||
private double _lastSendTime;
|
private double _lastSendTime;
|
||||||
private double _lastTickTime;
|
private double _lastTickTime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Did we write the dummy PacketInfo in the current SendBuffer
|
||||||
|
/// </summary>
|
||||||
|
private bool _bSendBufferHasDummyPacketInfo;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores the bit number where we wrote the dummy packet info in the packet header
|
||||||
|
/// </summary>
|
||||||
|
private FBitWriterMark _headerMarkForPacketInfo;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Timestamp of the last packet sent
|
||||||
|
/// </summary>
|
||||||
|
private double _previousPacketSendTimeInS;
|
||||||
|
|
||||||
|
private bool _bFlushedNetThisFrame;
|
||||||
|
private bool _bAutoFlush;
|
||||||
|
|
||||||
public UNetConnection()
|
public UNetConnection()
|
||||||
{
|
{
|
||||||
_channelsToTick = new HashSet<UChannel>();
|
_channelsToTick = new HashSet<UChannel>();
|
||||||
@@ -70,6 +94,10 @@ public abstract class UNetConnection : UPlayer
|
|||||||
ClientLoginState = EClientLoginState.Invalid;
|
ClientLoginState = EClientLoginState.Invalid;
|
||||||
ExpectedClientLoginMsgType = 0;
|
ExpectedClientLoginMsgType = 0;
|
||||||
PacketOverhead = 0;
|
PacketOverhead = 0;
|
||||||
|
Challenge = string.Empty;
|
||||||
|
SendBuffer = new FBitWriter(0);
|
||||||
|
OutLagTime = new double[256];
|
||||||
|
OutLagPacketId = new int[256];
|
||||||
InPacketId = -1;
|
InPacketId = -1;
|
||||||
OutPacketId = 0;
|
OutPacketId = 0;
|
||||||
OutAckPacketId = -1;
|
OutAckPacketId = -1;
|
||||||
@@ -96,9 +124,9 @@ public abstract class UNetConnection : UPlayer
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Package map between local and remote. (negotiates net serialization)
|
/// Package map between local and remote. (negotiates net serialization)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public UPackageMapClient? PackageMap { get; private set; }
|
public UPackageMap? PackageMap { get; private set; }
|
||||||
|
|
||||||
public List<UChannel> OpenChannels { get; private set; }
|
public List<UChannel> OpenChannels { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maximum packet size.
|
/// Maximum packet size.
|
||||||
@@ -115,11 +143,44 @@ public abstract class UNetConnection : UPlayer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public IPEndPoint? RemoteAddr { get; protected set; }
|
public IPEndPoint? RemoteAddr { get; protected set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Number of bits used for the packet id in the current packet.
|
||||||
|
/// </summary>
|
||||||
|
public int NumPacketIdBits { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Number of bits used for bunches in the current packet.
|
||||||
|
/// </summary>
|
||||||
|
public int NumBunchBits { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Number of bits used for acks in the current packet.
|
||||||
|
/// </summary>
|
||||||
|
public int NumAckBits { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Number of bits used for padding in the current packet.
|
||||||
|
/// </summary>
|
||||||
|
public int NumPaddingBits { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum number of bits all packet handlers will reserve.
|
||||||
|
/// </summary>
|
||||||
|
public int MaxPacketHandlerBits { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// State this connection is in.
|
/// State this connection is in.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public EConnectionState State { get; private set; }
|
public EConnectionState State { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This functionality is used during replay checkpoints for example, so we can re-use the existing connection and channels to record
|
||||||
|
/// a version of each actor and capture all properties that have changed since the actor has been alive...
|
||||||
|
/// This will also act as if it needs to re-open all the channels, etc.
|
||||||
|
/// NOTE - This doesn't force all exports to happen again though, it will only export new stuff, so keep that in mind.
|
||||||
|
/// </summary>
|
||||||
|
public EResendAllDataState ResendAllDataState { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// PacketHandler, for managing layered handler components, which modify packets as they are sent/received
|
/// PacketHandler, for managing layered handler components, which modify packets as they are sent/received
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -130,7 +191,7 @@ public abstract class UNetConnection : UPlayer
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to determine what the next expected control channel msg type should be from a connecting client
|
/// Used to determine what the next expected control channel msg type should be from a connecting client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public byte ExpectedClientLoginMsgType { get; private set; }
|
public NMT ExpectedClientLoginMsgType { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reference to the PacketHandler component, for managing stateless connection handshakes
|
/// Reference to the PacketHandler component, for managing stateless connection handshakes
|
||||||
@@ -142,6 +203,20 @@ public abstract class UNetConnection : UPlayer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int PacketOverhead { get; private set; }
|
public int PacketOverhead { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Server-generated challenge.
|
||||||
|
/// </summary>
|
||||||
|
public string Challenge { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queued up bits waiting to send
|
||||||
|
/// </summary>
|
||||||
|
public FBitWriter SendBuffer { get; private set; }
|
||||||
|
|
||||||
|
public double[] OutLagTime { get; }
|
||||||
|
|
||||||
|
public int[] OutLagPacketId { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Full incoming packet index.
|
/// Full incoming packet index.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -165,6 +240,38 @@ public abstract class UNetConnection : UPlayer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int LastNotifiedPacketId { get; private set; }
|
public int LastNotifiedPacketId { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Keep old behavior where we send a packet with only acks even if we have no other outgoing data if we got incoming data
|
||||||
|
/// </summary>
|
||||||
|
public uint HasDirtyAcks { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Most recently sent bunch start.
|
||||||
|
/// </summary>
|
||||||
|
public FBitWriterMark LastStart { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Most recently sent bunch end.
|
||||||
|
/// </summary>
|
||||||
|
public FBitWriterMark LastEnd { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether to allow merging.
|
||||||
|
/// </summary>
|
||||||
|
public bool AllowMerge { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether contents are time-sensitive.
|
||||||
|
/// </summary>
|
||||||
|
public bool TimeSensitive { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Most recent outgoing bunch.
|
||||||
|
/// </summary>
|
||||||
|
public FOutBunch? LastOutBunch { get; set; }
|
||||||
|
|
||||||
|
public FOutBunch LastOut { get; set; }
|
||||||
|
|
||||||
public int MaxChannelSize { get; }
|
public int MaxChannelSize { get; }
|
||||||
public UChannel?[] Channels { get; }
|
public UChannel?[] Channels { get; }
|
||||||
public int[] OutReliable { get; }
|
public int[] OutReliable { get; }
|
||||||
@@ -209,8 +316,9 @@ public abstract class UNetConnection : UPlayer
|
|||||||
|
|
||||||
InitHandler();
|
InitHandler();
|
||||||
|
|
||||||
PackageMap = new UPackageMapClient();
|
var packageMapClient = new UPackageMapClient();
|
||||||
PackageMap.Initialize(this, Driver.GuidCache);
|
packageMapClient.Initialize(this, Driver.GuidCache);
|
||||||
|
PackageMap = packageMapClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void InitRemoteConnection(UNetDriver inDriver, UdpClient inSocket, FUrl inURL, IPEndPoint inRemoteAddr, EConnectionState inState, int inMaxPacket = 0, int inPacketOverhead = 0);
|
public abstract void InitRemoteConnection(UNetDriver inDriver, UdpClient inSocket, FUrl inURL, IPEndPoint inRemoteAddr, EConnectionState inState, int inMaxPacket = 0, int inPacketOverhead = 0);
|
||||||
@@ -235,10 +343,13 @@ public abstract class UNetConnection : UPlayer
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// TODO: Close connection, malformed packet.
|
Logger.Fatal("Packet failed PacketHandler processing");
|
||||||
Logger.Fatal("Connection should be closed here");
|
Close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See if we receive a packet that wasn't fully consumed by the handler before the handler is initialized.
|
||||||
|
// TODO: Handler.IsFullyInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle an incoming raw packet from the driver.
|
// Handle an incoming raw packet from the driver.
|
||||||
@@ -273,7 +384,7 @@ public abstract class UNetConnection : UPlayer
|
|||||||
{
|
{
|
||||||
ReceivedPacket(reader);
|
ReceivedPacket(reader);
|
||||||
|
|
||||||
// TODO: Flush out of order cache
|
// TODO: FlushPacketOrderCache
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -301,8 +412,8 @@ public abstract class UNetConnection : UPlayer
|
|||||||
|
|
||||||
if (!PacketNotify.ReadHeader(ref header, reader))
|
if (!PacketNotify.ReadHeader(ref header, reader))
|
||||||
{
|
{
|
||||||
// TODO: Close connection, malformed packet.
|
|
||||||
Logger.Fatal("Failed to read PacketHeader");
|
Logger.Fatal("Failed to read PacketHeader");
|
||||||
|
Close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -516,7 +627,9 @@ public abstract class UNetConnection : UPlayer
|
|||||||
{
|
{
|
||||||
if (bunch.bReliable || bunch.bOpen)
|
if (bunch.bReliable || bunch.bOpen)
|
||||||
{
|
{
|
||||||
if (!UPackageMap.StaticSerializeName(reader, out var chName) || reader.IsError())
|
FName? chName = null;
|
||||||
|
|
||||||
|
if (!UPackageMap.StaticSerializeName(reader, ref chName) || reader.IsError())
|
||||||
{
|
{
|
||||||
// TODO: Close connection
|
// TODO: Close connection
|
||||||
Logger.Fatal("Channel name serialization failed");
|
Logger.Fatal("Channel name serialization failed");
|
||||||
@@ -773,10 +886,19 @@ public abstract class UNetConnection : UPlayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We do want to let the other side know about the ack, so even if there are no other outgoing data when we tick the connection we will send an ackpacket.
|
// We do want to let the other side know about the ack, so even if there are no other outgoing data when we tick the connection we will send an ackpacket.
|
||||||
// TimeSensitive = 1;
|
TimeSensitive = true;
|
||||||
// ++HasDirtyAcks;
|
++HasDirtyAcks;
|
||||||
|
|
||||||
// TODO: FlushNet if HasDirtyAcks
|
if (HasDirtyAcks >= FNetPacketNotify.MaxSequenceHistoryLength)
|
||||||
|
{
|
||||||
|
Logger.Warning("ReceivedPacket - Too many received packets to ack ({Acks}) since last sent packet. InSeq: {Seq} {Conn} NextOutGoingSeq: {OutSeq}", HasDirtyAcks, PacketNotify.GetInSeq().Value, this, PacketNotify.GetOutSeq().Value);
|
||||||
|
|
||||||
|
FlushNet();
|
||||||
|
if (HasDirtyAcks != 0)
|
||||||
|
{
|
||||||
|
FlushNet();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -874,12 +996,24 @@ public abstract class UNetConnection : UPlayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
Handler = new PacketHandler();
|
Handler = new PacketHandler();
|
||||||
Handler.Initialize(false);
|
Handler.Initialize(HandlerMode.Server /* Mode */, UNetConnection.MaxPacketSize * 8 ,false);
|
||||||
|
|
||||||
StatelessConnectComponent = (StatelessConnectHandlerComponent) Handler.AddHandler<StatelessConnectHandlerComponent>();
|
StatelessConnectComponent = (StatelessConnectHandlerComponent) Handler.AddHandler<StatelessConnectHandlerComponent>();
|
||||||
StatelessConnectComponent.SetDriver(Driver);
|
StatelessConnectComponent.SetDriver(Driver);
|
||||||
|
|
||||||
Handler.InitializeComponents();
|
Handler.InitializeComponents();
|
||||||
|
|
||||||
|
MaxPacketHandlerBits = Handler.GetTotalReservedPacketBits();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
// TODO: Implement
|
||||||
|
}
|
||||||
|
|
||||||
|
public long GetMaxSingleBunchSizeBits()
|
||||||
|
{
|
||||||
|
return (MaxPacket * 8) - MaxBunchHeaderBits - MaxPacketTrailerBits - MaxPacketHeaderBits - MaxPacketHandlerBits;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UChannel CreateChannelByName(FName chName, EChannelCreateFlags createFlags, int chIndex)
|
public UChannel CreateChannelByName(FName chName, EChannelCreateFlags createFlags, int chIndex)
|
||||||
@@ -933,6 +1067,455 @@ public abstract class UNetConnection : UPlayer
|
|||||||
return channel;
|
return channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SendChallengeControlMessage()
|
||||||
|
{
|
||||||
|
if (State != EConnectionState.USOCK_Invalid && State != EConnectionState.USOCK_Closed && Driver != null)
|
||||||
|
{
|
||||||
|
Challenge = "E660A966";
|
||||||
|
SetExpectedClientLoginMsgType(NMT.Login);
|
||||||
|
NMT_Challenge.Send(this, Challenge);
|
||||||
|
FlushNet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int SendRawBunch(FOutBunch bunch, bool inAllowMerge)
|
||||||
|
{
|
||||||
|
ValidateSendBuffer();
|
||||||
|
|
||||||
|
if (Driver == null || bunch.ReceivedAck || bunch.IsError())
|
||||||
|
{
|
||||||
|
throw new UnrealNetException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Increment things
|
||||||
|
TimeSensitive = true;
|
||||||
|
|
||||||
|
using var sendBunchHeader = new FBitWriter(MaxBunchHeaderBits);
|
||||||
|
|
||||||
|
var bIsOpenOrClose = bunch.bOpen || bunch.bClose;
|
||||||
|
var bIsOpenOrReliable = bunch.bOpen || bunch.bReliable;
|
||||||
|
|
||||||
|
sendBunchHeader.WriteBit(bIsOpenOrClose);
|
||||||
|
|
||||||
|
if (bIsOpenOrClose)
|
||||||
|
{
|
||||||
|
sendBunchHeader.WriteBit(bunch.bOpen);
|
||||||
|
sendBunchHeader.WriteBit(bunch.bClose);
|
||||||
|
|
||||||
|
if (bunch.bClose)
|
||||||
|
{
|
||||||
|
sendBunchHeader.SerializeInt((uint)bunch.CloseReason, (uint)EChannelCloseReason.MAX);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendBunchHeader.WriteBit(bunch.bIsReplicationPaused);
|
||||||
|
sendBunchHeader.WriteBit(bunch.bReliable);
|
||||||
|
|
||||||
|
sendBunchHeader.SerializeIntPacked((uint)bunch.ChIndex);
|
||||||
|
|
||||||
|
sendBunchHeader.WriteBit(bunch.bHasPackageMapExports);
|
||||||
|
sendBunchHeader.WriteBit(bunch.bHasMustBeMappedGUIDs);
|
||||||
|
sendBunchHeader.WriteBit(bunch.bPartial);
|
||||||
|
|
||||||
|
if (bunch.bReliable && !IsInternalAck())
|
||||||
|
{
|
||||||
|
// 14 > 24
|
||||||
|
sendBunchHeader.WriteIntWrapped((uint)bunch.ChSequence, MaxChSequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bunch.bPartial)
|
||||||
|
{
|
||||||
|
sendBunchHeader.WriteBit(bunch.bPartialInitial);
|
||||||
|
sendBunchHeader.WriteBit(bunch.bPartialFinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bIsOpenOrReliable)
|
||||||
|
{
|
||||||
|
var name = bunch.ChName;
|
||||||
|
UPackageMap.StaticSerializeName(sendBunchHeader, ref name);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendBunchHeader.WriteIntWrapped((uint)bunch.GetNumBits(), (uint)(MaxPacket * 8));
|
||||||
|
|
||||||
|
if (sendBunchHeader.IsError())
|
||||||
|
{
|
||||||
|
Logger.Fatal("SendBunchHeader Error: Bunch = {Bunch}", bunch);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remember start position.
|
||||||
|
AllowMerge = inAllowMerge;
|
||||||
|
bunch.Time = Driver.GetElapsedTime();
|
||||||
|
|
||||||
|
var bunchHeaderBits = sendBunchHeader.GetNumBits();
|
||||||
|
var bunchBits = bunch.GetNumBits();
|
||||||
|
|
||||||
|
// If the bunch does not fit in the current packet,
|
||||||
|
// flush packet now so that we can report collected stats in the correct scope
|
||||||
|
PrepareWriteBitsToSendBuffer(bunchHeaderBits, bunchBits);
|
||||||
|
|
||||||
|
// Write the bits to the buffer and remember the packet id used
|
||||||
|
bunch.PacketId = WriteBitsToSendBufferInternal(sendBunchHeader.GetData(), (int)bunchHeaderBits, bunch.GetData(), (int)bunchBits, EWriteBitsDataType.Bunch);
|
||||||
|
|
||||||
|
if (PackageMap != null && bunch.bHasPackageMapExports)
|
||||||
|
{
|
||||||
|
PackageMap.NotifyBunchCommit(bunch.PacketId, bunch);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bunch.bHasPackageMapExports)
|
||||||
|
{
|
||||||
|
// TOOD: Increment things
|
||||||
|
}
|
||||||
|
|
||||||
|
FlushNet();
|
||||||
|
|
||||||
|
return bunch.PacketId;
|
||||||
|
}
|
||||||
|
private void PrepareWriteBitsToSendBuffer(long sizeInBits, long extraSizeInBits)
|
||||||
|
{
|
||||||
|
ValidateSendBuffer();
|
||||||
|
|
||||||
|
var totalSizeInBits = sizeInBits + extraSizeInBits;
|
||||||
|
|
||||||
|
// Flush if we can't add to current buffer
|
||||||
|
if (totalSizeInBits > GetFreeSendBufferBits())
|
||||||
|
{
|
||||||
|
FlushNet();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is the start of the queue, make sure to add the packet id
|
||||||
|
if (SendBuffer.GetNumBits() == 0 && !IsInternalAck())
|
||||||
|
{
|
||||||
|
// Write Packet Header, before sending the packet we will go back and rewrite the data
|
||||||
|
WritePacketHeader(SendBuffer);
|
||||||
|
|
||||||
|
// Pre-write the bits for the packet info
|
||||||
|
WriteDummyPacketInfo(SendBuffer);
|
||||||
|
|
||||||
|
// We do not allow the first bunch to merge with the ack data as this will "revert" the ack data.
|
||||||
|
AllowMerge = false;
|
||||||
|
|
||||||
|
// Update stats for PacketIdBits and ackdata (also including the data used for packet RTT and saturation calculations)
|
||||||
|
var bitsWritten = (int)SendBuffer.GetNumBits();
|
||||||
|
|
||||||
|
NumPacketIdBits += FNetPacketNotify.SequenceNumberBits;
|
||||||
|
NumAckBits += bitsWritten - FNetPacketNotify.SequenceNumberBits;
|
||||||
|
|
||||||
|
ValidateSendBuffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WritePacketHeader(FBitWriter writer)
|
||||||
|
{
|
||||||
|
// If this is a header refresh, we only serialize the updated serial number information
|
||||||
|
var bIsHeaderUpdate = writer.GetNumBits() > 0;
|
||||||
|
|
||||||
|
// Header is always written first in the packet
|
||||||
|
var restore = new FBitWriterMark(writer);
|
||||||
|
|
||||||
|
writer.Num = 0;
|
||||||
|
|
||||||
|
// Write notification header or refresh the header if used space is the same.
|
||||||
|
var bWroteHeader = PacketNotify.WriteHeader(writer, bIsHeaderUpdate);
|
||||||
|
|
||||||
|
// Jump back to where we came from.
|
||||||
|
if (bIsHeaderUpdate)
|
||||||
|
{
|
||||||
|
restore.PopWithoutClear(writer);
|
||||||
|
|
||||||
|
// if we wrote the header and successfully refreshed the header status we no longer has any dirty acks
|
||||||
|
if (bWroteHeader)
|
||||||
|
{
|
||||||
|
HasDirtyAcks = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteFinalPacketInfo(FBitWriter writer, double packetSentTimeInS)
|
||||||
|
{
|
||||||
|
if (!_bSendBufferHasDummyPacketInfo)
|
||||||
|
{
|
||||||
|
// PacketInfo payload is not included in this SendBuffer; nothing to rewrite
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentMark = new FBitWriterMark(writer);
|
||||||
|
|
||||||
|
// Go back to write over the dummy bits
|
||||||
|
_headerMarkForPacketInfo.PopWithoutClear(writer);
|
||||||
|
|
||||||
|
// Write Jitter clock time
|
||||||
|
{
|
||||||
|
var deltaSendTimeInMS = (packetSentTimeInS - _previousPacketSendTimeInS) * 1000.0;
|
||||||
|
var clockTimeMilliseconds = 0;
|
||||||
|
|
||||||
|
// If the delta is over our max precision, we send MAX value and jitter will be ignored by the receiver.
|
||||||
|
if (deltaSendTimeInMS >= MaxJitterPrecisionInMS)
|
||||||
|
{
|
||||||
|
clockTimeMilliseconds = MaxJitterClockTimeValue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: Proper
|
||||||
|
// Get the fractional part (milliseconds) of the clock time
|
||||||
|
clockTimeMilliseconds = 0;
|
||||||
|
|
||||||
|
// Ensure we don't overflow
|
||||||
|
clockTimeMilliseconds &= MaxJitterClockTimeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.SerializeInt((uint)clockTimeMilliseconds, MaxJitterClockTimeValue + 1);
|
||||||
|
|
||||||
|
_previousPacketSendTimeInS = packetSentTimeInS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write server frame time
|
||||||
|
{
|
||||||
|
var bHasServerFrameTime = LastHasServerFrameTime;
|
||||||
|
writer.WriteBit(bHasServerFrameTime);
|
||||||
|
|
||||||
|
if (bHasServerFrameTime && Driver!.IsServer())
|
||||||
|
{
|
||||||
|
// Write data used to calculate link latency
|
||||||
|
// TODO: Proper
|
||||||
|
const int FrameTime = 123;
|
||||||
|
var frameTimeByte = (byte) Math.Min(Math.Floor((double)(FrameTime * 1000)), 255);
|
||||||
|
writer.WriteByte(frameTimeByte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_headerMarkForPacketInfo.Reset();
|
||||||
|
|
||||||
|
// Revert to the correct bit writing place
|
||||||
|
currentMark.PopWithoutClear(writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteDummyPacketInfo(FBitWriter writer)
|
||||||
|
{
|
||||||
|
var bHasPacketInfoPayload = _bFlushedNetThisFrame == false;
|
||||||
|
|
||||||
|
writer.WriteBit(bHasPacketInfoPayload);
|
||||||
|
|
||||||
|
if (bHasPacketInfoPayload)
|
||||||
|
{
|
||||||
|
// Pre-insert the bits since the final time values will be calculated and inserted right before LowLevelSend
|
||||||
|
_headerMarkForPacketInfo.Init(writer);
|
||||||
|
|
||||||
|
Span<byte> dummyJitterClockTime = stackalloc byte[4];
|
||||||
|
writer.SerializeBits(dummyJitterClockTime, NumBitsForJitterClockTimeInHeader);
|
||||||
|
|
||||||
|
var bHasServerFrameTime = LastHasServerFrameTime;
|
||||||
|
writer.WriteBit(bHasServerFrameTime);
|
||||||
|
|
||||||
|
if (bHasServerFrameTime && Driver!.IsServer()) // false
|
||||||
|
{
|
||||||
|
const byte dummyFrameTimeByte = 0;
|
||||||
|
writer.WriteByte(dummyFrameTimeByte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_bSendBufferHasDummyPacketInfo = bHasPacketInfoPayload;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int WriteBitsToSendBufferInternal(byte[]? bits, int sizeInBits, byte[]? extraBits, int extraSizeInBits, EWriteBitsDataType dataType)
|
||||||
|
{
|
||||||
|
// Remember start position in case we want to undo this write, no meaning to undo the header write as this is only used to pop bunches and the header should not count towards the bunch
|
||||||
|
// Store this after the possible flush above so we have the correct start position in the case that we do flush
|
||||||
|
LastStart = new FBitWriterMark(SendBuffer);
|
||||||
|
|
||||||
|
// Add the bits to the queue
|
||||||
|
if (sizeInBits != 0)
|
||||||
|
{
|
||||||
|
if (bits == null)
|
||||||
|
{
|
||||||
|
throw new UnrealNetException("bits should not be null if a size is set");
|
||||||
|
}
|
||||||
|
|
||||||
|
SendBuffer.SerializeBits(bits, sizeInBits);
|
||||||
|
ValidateSendBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add any extra bits
|
||||||
|
if (extraSizeInBits != 0)
|
||||||
|
{
|
||||||
|
if (extraBits == null)
|
||||||
|
{
|
||||||
|
throw new UnrealNetException("extraBits should not be null if a size is set");
|
||||||
|
}
|
||||||
|
|
||||||
|
SendBuffer.SerializeBits(extraBits, extraSizeInBits);
|
||||||
|
ValidateSendBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 242 after
|
||||||
|
var rememberedPacketId = OutPacketId;
|
||||||
|
|
||||||
|
if (dataType == EWriteBitsDataType.Bunch)
|
||||||
|
{
|
||||||
|
NumBunchBits += sizeInBits + extraSizeInBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush now if we are full
|
||||||
|
if (GetFreeSendBufferBits() == 0)
|
||||||
|
{
|
||||||
|
FlushNet();
|
||||||
|
}
|
||||||
|
|
||||||
|
return rememberedPacketId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int WriteBitsToSendBuffer(byte[]? bits, int sizeInBits, byte[]? extraBits = null, int extraSizeInBits = 0, EWriteBitsDataType dataType = EWriteBitsDataType.Unknown)
|
||||||
|
{
|
||||||
|
// Flush packet as needed and begin new packet
|
||||||
|
PrepareWriteBitsToSendBuffer(sizeInBits, extraSizeInBits);
|
||||||
|
|
||||||
|
// Write the data and flush if the packet is full, return value is the packetId into which the data was written
|
||||||
|
return WriteBitsToSendBufferInternal(bits, sizeInBits, extraBits, extraSizeInBits, dataType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns number of bits left in current packet that can be used without causing a flush
|
||||||
|
/// </summary>
|
||||||
|
private long GetFreeSendBufferBits()
|
||||||
|
{
|
||||||
|
// If we haven't sent anything yet, make sure to account for the packet header + trailer size
|
||||||
|
// Otherwise, we only need to account for trailer size
|
||||||
|
var extraBits = (SendBuffer.GetNumBits() > 0) ? MaxPacketTrailerBits : MaxPacketHeaderBits + MaxPacketTrailerBits;
|
||||||
|
var numberOfFreeBits = SendBuffer.GetMaxBits() - (SendBuffer.GetNumBits() + extraBits);
|
||||||
|
|
||||||
|
return numberOfFreeBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ValidateSendBuffer()
|
||||||
|
{
|
||||||
|
if (SendBuffer.IsError())
|
||||||
|
{
|
||||||
|
Logger.Fatal("ValidateSendBuffer: Out.IsError() == true. NumBits: {A}, NumBytes: {B}, MaxBits: {C}", SendBuffer.GetNumBits(), SendBuffer.GetNumBytes(), SendBuffer.GetMaxBits());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private protected void InitSendBuffer()
|
||||||
|
{
|
||||||
|
var finalBufferSize = (MaxPacket * 8) - MaxPacketHandlerBits;
|
||||||
|
if (finalBufferSize == SendBuffer.GetMaxBits())
|
||||||
|
{
|
||||||
|
SendBuffer.Reset();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SendBuffer = new FBitWriter(finalBufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
_headerMarkForPacketInfo.Reset();
|
||||||
|
|
||||||
|
ResetPacketBitCounts();
|
||||||
|
|
||||||
|
ValidateSendBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetPacketBitCounts()
|
||||||
|
{
|
||||||
|
NumPacketIdBits = 0;
|
||||||
|
NumBunchBits = 0;
|
||||||
|
NumAckBits = 0;
|
||||||
|
NumPaddingBits = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FlushNet()
|
||||||
|
{
|
||||||
|
if (Driver == null)
|
||||||
|
{
|
||||||
|
throw new UnrealNetException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update info.
|
||||||
|
ValidateSendBuffer();
|
||||||
|
LastEnd = new FBitWriterMark();
|
||||||
|
TimeSensitive = false;
|
||||||
|
|
||||||
|
// If there is any pending data to send, send it.
|
||||||
|
if (SendBuffer.GetNumBits() != 0 || HasDirtyAcks != 0 || (Driver.GetElapsedTime() - _lastSendTime > Driver.KeepAliveTime && !IsInternalAck() && State != EConnectionState.USOCK_Closed))
|
||||||
|
{
|
||||||
|
// Due to the PacketHandler handshake code, servers must never send the client data,
|
||||||
|
// before first receiving a client control packet (which is taken as an indication of a complete handshake).
|
||||||
|
if (!HasReceivedClientPacket())
|
||||||
|
{
|
||||||
|
Logger.Debug("Attempting to send data before handshake is complete. {Channel}", this);
|
||||||
|
Close();
|
||||||
|
InitSendBuffer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var traits = new FOutPacketTraits();
|
||||||
|
|
||||||
|
// If sending keepalive packet or just acks, still write the packet header
|
||||||
|
if (SendBuffer.GetNumBits() == 0)
|
||||||
|
{
|
||||||
|
WriteBitsToSendBuffer(null, 0); // This will force the packet header to be written
|
||||||
|
|
||||||
|
traits.IsKeepAlive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Handler != null)
|
||||||
|
{
|
||||||
|
Handler.OutgoingHigh(SendBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
var packetSentTimeInS = FPlatformTime.Seconds();
|
||||||
|
|
||||||
|
// Write the UNetConnection-level termination bit
|
||||||
|
SendBuffer.WriteBit(true);
|
||||||
|
|
||||||
|
// Refresh outgoing header with latest data
|
||||||
|
if (!IsInternalAck())
|
||||||
|
{
|
||||||
|
// if we update ack, we also update received ack associated with outgoing seq
|
||||||
|
// so we know how many ack bits we need to write (which is updated in received packet)
|
||||||
|
WritePacketHeader(SendBuffer);
|
||||||
|
|
||||||
|
WriteFinalPacketInfo(SendBuffer, packetSentTimeInS);
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidateSendBuffer();
|
||||||
|
|
||||||
|
var numStrayBits = SendBuffer.GetNumBits();
|
||||||
|
|
||||||
|
traits.NumAckBits = (uint)NumAckBits;
|
||||||
|
traits.NumBunchBits = (uint)NumBunchBits;
|
||||||
|
|
||||||
|
// Removed packet emulation
|
||||||
|
|
||||||
|
if (Driver.IsNetResourceValid())
|
||||||
|
{
|
||||||
|
LowLevelSend(SendBuffer.GetData(), (int)SendBuffer.GetNumBits(), traits);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update stuff.
|
||||||
|
var index = OutPacketId & (OutLagPacketId.Length - 1);
|
||||||
|
|
||||||
|
// Remember the actual time this packet was sent out, so we can compute ping when the ack comes back
|
||||||
|
OutLagPacketId[index] = OutPacketId;
|
||||||
|
OutLagTime[index] = packetSentTimeInS;
|
||||||
|
|
||||||
|
// Increase outgoing sequence number
|
||||||
|
if (!IsInternalAck())
|
||||||
|
{
|
||||||
|
PacketNotify.CommitAndIncrementOutSeq();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Make sure that we always push an ChannelRecordEntry for each transmitted packet even if it is empty
|
||||||
|
|
||||||
|
++OutPacketId;
|
||||||
|
|
||||||
|
// TODO: Increment things
|
||||||
|
|
||||||
|
_lastSendTime = Driver.GetElapsedTime();
|
||||||
|
|
||||||
|
_bFlushedNetThisFrame = true;
|
||||||
|
|
||||||
|
InitSendBuffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private int GetFreeChannelIndex(FName chName)
|
private int GetFreeChannelIndex(FName chName)
|
||||||
{
|
{
|
||||||
int chIndex;
|
int chIndex;
|
||||||
@@ -984,7 +1567,7 @@ public abstract class UNetConnection : UPlayer
|
|||||||
ClientLoginState = newState;
|
ClientLoginState = newState;
|
||||||
}
|
}
|
||||||
|
|
||||||
private protected void SetExpectedClientLoginMsgType(byte newType)
|
private protected void SetExpectedClientLoginMsgType(NMT newType)
|
||||||
{
|
{
|
||||||
if (ExpectedClientLoginMsgType == newType)
|
if (ExpectedClientLoginMsgType == newType)
|
||||||
{
|
{
|
||||||
@@ -997,6 +1580,33 @@ public abstract class UNetConnection : UPlayer
|
|||||||
ExpectedClientLoginMsgType = newType;
|
ExpectedClientLoginMsgType = newType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsClientMsgTypeValid(NMT clientMsgType)
|
||||||
|
{
|
||||||
|
if (ClientLoginState == EClientLoginState.LoggingIn)
|
||||||
|
{
|
||||||
|
if (clientMsgType != ExpectedClientLoginMsgType)
|
||||||
|
{
|
||||||
|
Logger.Debug("IsClientMsgTypeValid FAILED: (ClientMsgType != ExpectedClientLoginMsgType) Remote Address={Address}", LowLevelGetRemoteAddress());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (clientMsgType == NMT.Hello || clientMsgType == NMT.Login)
|
||||||
|
{
|
||||||
|
Logger.Debug("IsClientMsgTypeValid FAILED: Invalid msg after being logged in - Remote Address={Address}", LowLevelGetRemoteAddress());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HasReceivedClientPacket()
|
||||||
|
{
|
||||||
|
return IsInternalAck() || !Driver!.IsServer() || InReliable[0] != InitInReliable;
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsInternalAck()
|
public bool IsInternalAck()
|
||||||
{
|
{
|
||||||
return _bInternalAck;
|
return _bInternalAck;
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ using System.Net;
|
|||||||
using Prospect.Unreal.Core.Names;
|
using Prospect.Unreal.Core.Names;
|
||||||
using Prospect.Unreal.Exceptions;
|
using Prospect.Unreal.Exceptions;
|
||||||
using Prospect.Unreal.Net.Channels;
|
using Prospect.Unreal.Net.Channels;
|
||||||
|
using Prospect.Unreal.Net.Channels.Actor;
|
||||||
|
using Prospect.Unreal.Net.Channels.Control;
|
||||||
|
using Prospect.Unreal.Net.Channels.Voice;
|
||||||
using Prospect.Unreal.Runtime;
|
using Prospect.Unreal.Runtime;
|
||||||
|
|
||||||
namespace Prospect.Unreal.Net;
|
namespace Prospect.Unreal.Net;
|
||||||
@@ -31,6 +34,9 @@ public abstract class UNetDriver : IAsyncDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// From Engine ini
|
||||||
|
public float KeepAliveTime { get; } = 0.2f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// World this net driver is associated with
|
/// World this net driver is associated with
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -73,7 +79,7 @@ public abstract class UNetDriver : IAsyncDisposable
|
|||||||
public void InitConnectionlessHandler()
|
public void InitConnectionlessHandler()
|
||||||
{
|
{
|
||||||
ConnectionlessHandler = new PacketHandler();
|
ConnectionlessHandler = new PacketHandler();
|
||||||
ConnectionlessHandler.Initialize(true);
|
ConnectionlessHandler.Initialize(HandlerMode.Server, UNetConnection.MaxPacketSize, true);
|
||||||
|
|
||||||
StatelessConnectComponent = (StatelessConnectHandlerComponent) ConnectionlessHandler.AddHandler<StatelessConnectHandlerComponent>();
|
StatelessConnectComponent = (StatelessConnectHandlerComponent) ConnectionlessHandler.AddHandler<StatelessConnectHandlerComponent>();
|
||||||
StatelessConnectComponent.SetDriver(this);
|
StatelessConnectComponent.SetDriver(this);
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Prospect.Unreal.Core;
|
using Prospect.Unreal.Core;
|
||||||
using Prospect.Unreal.Core.Names;
|
using Prospect.Unreal.Core.Names;
|
||||||
|
using Prospect.Unreal.Net.Packets.Bunch;
|
||||||
using Prospect.Unreal.Serialization;
|
using Prospect.Unreal.Serialization;
|
||||||
|
|
||||||
namespace Prospect.Unreal.Net;
|
namespace Prospect.Unreal.Net;
|
||||||
|
|
||||||
public class UPackageMap
|
public class UPackageMap
|
||||||
{
|
{
|
||||||
public static unsafe bool StaticSerializeName(FBitReader ar, [MaybeNullWhen(false)] out FName name)
|
public static unsafe bool StaticSerializeName(FArchive ar, [MaybeNullWhen(false)] ref FName name)
|
||||||
{
|
{
|
||||||
if (!ar.IsLoading())
|
if (ar.IsLoading())
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
name = null;
|
name = null;
|
||||||
|
|
||||||
bool bHardcoded;
|
bool bHardcoded;
|
||||||
@@ -50,7 +48,36 @@ public class UPackageMap
|
|||||||
var inNumber = ar.ReadInt32();
|
var inNumber = ar.ReadInt32();
|
||||||
name = new FName(inString, inNumber);
|
name = new FName(inString, inNumber);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var bHardcoded = (byte)(ShouldReplicateAsInteger(name) ? 1 : 0);
|
||||||
|
ar.SerializeBits(&bHardcoded, 1); // 25
|
||||||
|
if (bHardcoded != 0)
|
||||||
|
{
|
||||||
|
ar.SerializeIntPacked((uint)name.Number);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// send by string
|
||||||
|
var outString = name.Str;
|
||||||
|
var outNumber = name.Number;
|
||||||
|
|
||||||
|
ar.WriteString(outString);
|
||||||
|
ar.WriteInt32(outNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool ShouldReplicateAsInteger(FName name)
|
||||||
|
{
|
||||||
|
return name.Number <= UnrealNames.MaxNetworkedHardcodedName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void NotifyBunchCommit(int bunchPacketId, FOutBunch bunch)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace Prospect.Unreal.Net;
|
namespace Prospect.Unreal.Net;
|
||||||
|
|
||||||
public class UPackageMapClient
|
public class UPackageMapClient : UPackageMap
|
||||||
{
|
{
|
||||||
public void Initialize(UNetConnection connection, FNetGUIDCache guidCache)
|
public void Initialize(UNetConnection connection, FNetGUIDCache guidCache)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
@@ -7,8 +7,24 @@
|
|||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
||||||
|
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<!--Don't include the output from a previous source generator execution into future runs; the */** trick here ensures that there's -->
|
||||||
|
<!--at least one subdirectory, which is our key that it's coming from a source generator as opposed to something that is coming from -->
|
||||||
|
<!--some other tool. -->
|
||||||
|
<Compile Remove="$(CompilerGeneratedFilesOutputPath)/*/**/*.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Serilog" Version="2.10.0" />
|
<PackageReference Include="Serilog" Version="2.10.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Prospect.Unreal.Generator\Prospect.Unreal.Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
using Prospect.Unreal.Core;
|
using Prospect.Unreal.Core;
|
||||||
|
using Prospect.Unreal.Exceptions;
|
||||||
using Prospect.Unreal.Net;
|
using Prospect.Unreal.Net;
|
||||||
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 Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Prospect.Unreal.Runtime;
|
namespace Prospect.Unreal.Runtime;
|
||||||
@@ -60,7 +62,12 @@ public abstract class UWorld : FNetworkNotify, IAsyncDisposable
|
|||||||
|
|
||||||
public bool NotifyAcceptingChannel(UChannel channel)
|
public bool NotifyAcceptingChannel(UChannel channel)
|
||||||
{
|
{
|
||||||
var driver = channel.Connection.Driver!;
|
if (channel.Connection?.Driver == null)
|
||||||
|
{
|
||||||
|
throw new UnrealNetException();
|
||||||
|
}
|
||||||
|
|
||||||
|
var driver = channel.Connection.Driver;
|
||||||
if (!driver.IsServer())
|
if (!driver.IsServer())
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("Client code");
|
throw new NotSupportedException("Client code");
|
||||||
@@ -81,9 +88,59 @@ public abstract class UWorld : FNetworkNotify, IAsyncDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void NotifyControlMessage(UNetConnection connection, byte messageType, FInBunch bunch)
|
public void NotifyControlMessage(UNetConnection connection, NMT messageType, FInBunch bunch)
|
||||||
{
|
{
|
||||||
|
if (NetDriver == null)
|
||||||
|
{
|
||||||
|
throw new UnrealNetException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!NetDriver.IsServer())
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Client code");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Verbose("Level server received: {MessageType}", messageType);
|
||||||
|
|
||||||
|
if (!connection.IsClientMsgTypeValid(messageType))
|
||||||
|
{
|
||||||
|
Logger.Error("IsClientMsgTypeValid FAILED ({MessageType}): Remote Address = {Address}", (int)messageType, connection.LowLevelGetRemoteAddress());
|
||||||
|
bunch.SetError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (messageType)
|
||||||
|
{
|
||||||
|
case NMT.Hello:
|
||||||
|
{
|
||||||
|
if (NMT_Hello.Receive(bunch, out var isLittleEndian, out var remoteNetworkVersion, out var encryptionToken))
|
||||||
|
{
|
||||||
|
// TODO: Version check.
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(encryptionToken))
|
||||||
|
{
|
||||||
|
connection.SendChallengeControlMessage();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new NotImplementedException("Encryption");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case NMT.Login:
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
throw new NotImplementedException($"Unhandled control message {messageType}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask DisposeAsync()
|
public async ValueTask DisposeAsync()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Prospect.Unreal.Core;
|
using System.Runtime.CompilerServices;
|
||||||
|
using Prospect.Unreal.Core;
|
||||||
|
|
||||||
namespace Prospect.Unreal.Serialization;
|
namespace Prospect.Unreal.Serialization;
|
||||||
|
|
||||||
@@ -179,6 +180,50 @@ public abstract class FArchive : IDisposable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected uint _arGameNetVer;
|
protected uint _arGameNetVer;
|
||||||
|
|
||||||
|
public FArchive()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public FArchive(FArchive archive)
|
||||||
|
{
|
||||||
|
_arIsLoading = archive._arIsLoading;
|
||||||
|
_arIsSaving = archive._arIsSaving;
|
||||||
|
_arIsTransacting = archive._arIsTransacting;
|
||||||
|
_arIsTextFormat = archive._arIsTextFormat;
|
||||||
|
_arWantBinaryPropertySerialization = archive._arWantBinaryPropertySerialization;
|
||||||
|
_arForceUnicode = archive._arForceUnicode;
|
||||||
|
_arIsPersistent = archive._arIsPersistent;
|
||||||
|
_arIsError = archive._arIsError;
|
||||||
|
_arIsCriticalError = archive._arIsCriticalError;
|
||||||
|
_arContainsCode = archive._arContainsCode;
|
||||||
|
_arContainsMap = archive._arContainsMap;
|
||||||
|
_arRequiresLocalizationGather = archive._arRequiresLocalizationGather;
|
||||||
|
_arForceByteSwapping = archive._arForceByteSwapping;
|
||||||
|
_arIgnoreArchetypeRef = archive._arIgnoreArchetypeRef;
|
||||||
|
_arNoDelta = archive._arNoDelta;
|
||||||
|
_arIgnoreOuterRef = archive._arIgnoreOuterRef;
|
||||||
|
_arIgnoreClassGeneratedByRef = archive._arIgnoreClassGeneratedByRef;
|
||||||
|
_arIgnoreClassRef = archive._arIgnoreClassRef;
|
||||||
|
_arAllowLazyLoading = archive._arAllowLazyLoading;
|
||||||
|
_arIsObjectReferenceCollector = archive._arIsObjectReferenceCollector;
|
||||||
|
_arIsModifyingWeakAndStrongReferences = archive._arIsModifyingWeakAndStrongReferences;
|
||||||
|
_arIsCountingMemory = archive._arIsCountingMemory;
|
||||||
|
_arShouldSkipBulkData = archive._arShouldSkipBulkData;
|
||||||
|
_arIsFilterEditorOnly = archive._arIsFilterEditorOnly;
|
||||||
|
_arIsSaveGame = archive._arIsSaveGame;
|
||||||
|
_arIsNetArchive = archive._arIsNetArchive;
|
||||||
|
_arUseCustomPropertyList = archive._arUseCustomPropertyList;
|
||||||
|
_arSerializingDefaults = archive._arSerializingDefaults;
|
||||||
|
_arPortFlags = archive._arPortFlags;
|
||||||
|
_arMaxSerializeSize = archive._arMaxSerializeSize;
|
||||||
|
_arUE4Ver = archive._arUE4Ver;
|
||||||
|
_arLicenseeUE4Ver = archive._arLicenseeUE4Ver;
|
||||||
|
_arEngineVer = archive._arEngineVer;
|
||||||
|
_arEngineNetVer = archive._arEngineNetVer;
|
||||||
|
_arGameNetVer = archive._arGameNetVer;
|
||||||
|
}
|
||||||
|
|
||||||
public virtual bool ReadBit()
|
public virtual bool ReadBit()
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
@@ -191,6 +236,11 @@ public abstract class FArchive : IDisposable
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public virtual unsafe void WriteByte(byte value)
|
||||||
|
{
|
||||||
|
Serialize(&value, 1);
|
||||||
|
}
|
||||||
|
|
||||||
public virtual unsafe byte[] ReadBytes(long amount)
|
public virtual unsafe byte[] ReadBytes(long amount)
|
||||||
{
|
{
|
||||||
byte[] value = new byte[amount];
|
byte[] value = new byte[amount];
|
||||||
@@ -224,6 +274,11 @@ public abstract class FArchive : IDisposable
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public virtual unsafe void WriteUInt32(uint value)
|
||||||
|
{
|
||||||
|
ByteOrderSerialize(&value, sizeof(uint));
|
||||||
|
}
|
||||||
|
|
||||||
public virtual unsafe int ReadInt32()
|
public virtual unsafe int ReadInt32()
|
||||||
{
|
{
|
||||||
int value;
|
int value;
|
||||||
@@ -312,6 +367,14 @@ public abstract class FArchive : IDisposable
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public unsafe void SerializeBits(Span<byte> value, long lengthBits)
|
||||||
|
{
|
||||||
|
fixed (byte* pBuffer = value)
|
||||||
|
{
|
||||||
|
SerializeBits(pBuffer, lengthBits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public unsafe void SerializeBits(byte[] value, long lengthBits)
|
public unsafe void SerializeBits(byte[] value, long lengthBits)
|
||||||
{
|
{
|
||||||
fixed (byte* pBuffer = value)
|
fixed (byte* pBuffer = value)
|
||||||
@@ -320,9 +383,6 @@ public abstract class FArchive : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value"></param>
|
/// <param name="value"></param>
|
||||||
/// <param name="lengthBits"></param>
|
/// <param name="lengthBits"></param>
|
||||||
public virtual unsafe void SerializeBits(void* value, long lengthBits)
|
public virtual unsafe void SerializeBits(void* value, long lengthBits)
|
||||||
@@ -335,10 +395,21 @@ public abstract class FArchive : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual unsafe void SerializeInt(uint* value, uint max)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public unsafe void SerializeInt(uint value, uint valueMax)
|
||||||
|
{
|
||||||
|
SerializeInt(&value, valueMax);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual unsafe void SerializeInt(uint* value, uint valueMax)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
ByteOrderSerialize(&value, sizeof(uint));
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public unsafe void SerializeIntPacked(uint value)
|
||||||
|
{
|
||||||
|
SerializeIntPacked(&value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -727,5 +798,10 @@ public abstract class FArchive : IDisposable
|
|||||||
_arIsFilterEditorOnly = inFilterEditorOnly;
|
_arIsFilterEditorOnly = inFilterEditorOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public virtual void Reset()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public abstract void Dispose();
|
public abstract void Dispose();
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
namespace Prospect.Unreal.Serialization;
|
namespace Prospect.Unreal.Serialization;
|
||||||
|
|
||||||
public struct FBitReaderMark
|
public readonly struct FBitReaderMark
|
||||||
{
|
{
|
||||||
private long _pos;
|
private readonly long _pos;
|
||||||
|
|
||||||
public FBitReaderMark(FBitReader reader)
|
public FBitReaderMark(FBitReader reader)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using Prospect.Unreal.Core;
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Prospect.Unreal.Serialization;
|
namespace Prospect.Unreal.Serialization;
|
||||||
@@ -10,27 +9,102 @@ public class FBitWriter : FArchive
|
|||||||
private static readonly ILogger Logger = Log.ForContext<FBitWriter>();
|
private static readonly ILogger Logger = Log.ForContext<FBitWriter>();
|
||||||
private static readonly ArrayPool<byte> Pool = ArrayPool<byte>.Create();
|
private static readonly ArrayPool<byte> Pool = ArrayPool<byte>.Create();
|
||||||
|
|
||||||
public FBitWriter(int inMaxBits, bool inAllowResize = false)
|
private readonly bool _usesPool;
|
||||||
|
|
||||||
|
public FBitWriter()
|
||||||
|
{
|
||||||
|
Num = 0;
|
||||||
|
Max = 0;
|
||||||
|
Data = Array.Empty<byte>();
|
||||||
|
AllowResize = false;
|
||||||
|
AllowOverflow = false;
|
||||||
|
|
||||||
|
_usesPool = false;
|
||||||
|
_arIsSaving = true;
|
||||||
|
_arIsPersistent = true;
|
||||||
|
_arIsNetArchive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FBitWriter(long inMaxBits, bool inAllowResize = false, bool usePool = true)
|
||||||
{
|
{
|
||||||
Num = 0;
|
Num = 0;
|
||||||
Max = inMaxBits;
|
Max = inMaxBits;
|
||||||
AllowResize = inAllowResize;
|
AllowResize = inAllowResize;
|
||||||
Buffer = Pool.Rent((inMaxBits + 7) >> 3);
|
|
||||||
|
var byteCount = (int)((inMaxBits + 7) >> 3);
|
||||||
|
if (usePool)
|
||||||
|
{
|
||||||
|
Data = Pool.Rent(byteCount);
|
||||||
|
_usesPool = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Data = new byte[byteCount];
|
||||||
|
_usesPool = false;
|
||||||
|
}
|
||||||
|
|
||||||
_arIsSaving = true;
|
_arIsSaving = true;
|
||||||
_arIsPersistent = true;
|
_arIsPersistent = true;
|
||||||
_arIsNetArchive = true;
|
_arIsNetArchive = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] Buffer { get; set; }
|
public FBitWriter(FBitWriter writer) : base(writer)
|
||||||
private long Num { get; set; }
|
{
|
||||||
|
Num = writer.Num;
|
||||||
|
Max = writer.Max;
|
||||||
|
AllowOverflow = writer.AllowOverflow;
|
||||||
|
AllowResize = writer.AllowResize;
|
||||||
|
|
||||||
|
if (writer.Data.Length > 0)
|
||||||
|
{
|
||||||
|
_usesPool = writer._usesPool;
|
||||||
|
|
||||||
|
if (_usesPool)
|
||||||
|
{
|
||||||
|
Data = Pool.Rent(writer.Data.Length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Data = new byte[writer.Data.Length];
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer.BlockCopy(writer.Data, 0, Data, 0, writer.Data.Length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Data = Array.Empty<byte>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] Data { get; set; }
|
||||||
|
internal long Num { get; set; }
|
||||||
private long Max { get; set; }
|
private long Max { get; set; }
|
||||||
private bool AllowResize { get; }
|
private bool AllowResize { get; set; }
|
||||||
private bool AllowOverflow { get; }
|
private bool AllowOverflow { get; }
|
||||||
|
|
||||||
public override void Dispose()
|
public byte[] GetData()
|
||||||
{
|
{
|
||||||
Pool.Return(Buffer, true);
|
if (IsError())
|
||||||
|
{
|
||||||
|
Logger.Error("Retrieved data from a BitWriter that had an error");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long GetNumBytes()
|
||||||
|
{
|
||||||
|
return (Num + 7) >> 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long GetNumBits()
|
||||||
|
{
|
||||||
|
return Num;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long GetMaxBits()
|
||||||
|
{
|
||||||
|
return Max;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override unsafe void Serialize(void* src, long lengthBytes)
|
public override unsafe void Serialize(void* src, long lengthBytes)
|
||||||
@@ -38,7 +112,7 @@ public class FBitWriter : FArchive
|
|||||||
var lengthBits = lengthBytes * 8;
|
var lengthBits = lengthBytes * 8;
|
||||||
if (AllowAppend(lengthBits))
|
if (AllowAppend(lengthBits))
|
||||||
{
|
{
|
||||||
fixed (byte* pBuffer = Buffer)
|
fixed (byte* pBuffer = Data)
|
||||||
{
|
{
|
||||||
FBitUtil.AppBitsCpy(pBuffer, (int)Num, (byte*)src, 0, (int)lengthBits);
|
FBitUtil.AppBitsCpy(pBuffer, (int)Num, (byte*)src, 0, (int)lengthBits);
|
||||||
}
|
}
|
||||||
@@ -51,6 +125,34 @@ public class FBitWriter : FArchive
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override unsafe void SerializeBits(void* value, long lengthBits)
|
||||||
|
{
|
||||||
|
if (AllowAppend(lengthBits))
|
||||||
|
{
|
||||||
|
if (lengthBits == 1)
|
||||||
|
{
|
||||||
|
if ((((byte*)value)[0] & 0x01) != 0)
|
||||||
|
{
|
||||||
|
Data[Num >> 3] |= FBitUtil.GShift[Num & 7];
|
||||||
|
}
|
||||||
|
|
||||||
|
Num++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fixed (byte* pBuffer = Data)
|
||||||
|
{
|
||||||
|
FBitUtil.AppBitsCpy(pBuffer, (int)Num, (byte*)value, 0, (int)lengthBits);
|
||||||
|
Num += lengthBits;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetOverflowed(lengthBits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void SerializeBits(BitArray bits, int lengthBits)
|
public void SerializeBits(BitArray bits, int lengthBits)
|
||||||
{
|
{
|
||||||
if (AllowAppend(lengthBits))
|
if (AllowAppend(lengthBits))
|
||||||
@@ -66,13 +168,134 @@ public class FBitWriter : FArchive
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override unsafe void SerializeInt(uint* value, uint valueMax)
|
||||||
|
{
|
||||||
|
if (valueMax < 2)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
var lengthBits = (int) Math.Ceiling(Math.Log2(valueMax));
|
||||||
|
var writeValue = *value;
|
||||||
|
if (writeValue >= valueMax)
|
||||||
|
{
|
||||||
|
Logger.Error("SerializeInt(): Value out of bounds (Value: {Value}, ValueMax: {ValueMax})", writeValue, valueMax);
|
||||||
|
|
||||||
|
writeValue = valueMax - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AllowAppend(lengthBits))
|
||||||
|
{
|
||||||
|
uint newValue = 0;
|
||||||
|
var localNum = Num;
|
||||||
|
|
||||||
|
for (uint mask = 1; (newValue + mask) < valueMax && (mask != 0); mask *= 2, localNum++)
|
||||||
|
{
|
||||||
|
if ((writeValue & mask) != 0)
|
||||||
|
{
|
||||||
|
Data[localNum >> 3] += FBitUtil.GShift[localNum & 7];
|
||||||
|
newValue += mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Num = localNum;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetOverflowed(lengthBits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override unsafe void SerializeIntPacked(uint* inValue)
|
||||||
|
{
|
||||||
|
uint value = *inValue;
|
||||||
|
Span<uint> bytesAsWords = stackalloc uint[5];
|
||||||
|
uint byteCount = 0;
|
||||||
|
|
||||||
|
for (uint It = 0; (It == 0) | (value != 0); ++It, value = value >> 7)
|
||||||
|
{
|
||||||
|
if ((value & ~0x7F) != 0)
|
||||||
|
{
|
||||||
|
bytesAsWords[(int)byteCount++] = ((value & 0x7FU) << 1) | 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bytesAsWords[(int)byteCount++] = ((value & 0x7FU) << 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var lengthBits = byteCount * 8;
|
||||||
|
if (!AllowAppend(lengthBits))
|
||||||
|
{
|
||||||
|
SetOverflowed(lengthBits);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int BitCountUsedInByte = (int)(Num & 7);
|
||||||
|
int BitCountLeftInByte = (int)(8 - (Num & 7));
|
||||||
|
byte DestMaskByte0 = (byte)((1U << BitCountUsedInByte) - 1U);
|
||||||
|
byte DestMaskByte1 = (byte)(0xFF ^ DestMaskByte0);
|
||||||
|
bool bStraddlesTwoBytes = (BitCountUsedInByte != 0);
|
||||||
|
|
||||||
|
fixed (byte* pData = Data)
|
||||||
|
{
|
||||||
|
var Dest = pData + (Num >> 3);
|
||||||
|
|
||||||
|
Num += lengthBits;
|
||||||
|
for (var ByteIt = 0; ByteIt != byteCount; ++ByteIt)
|
||||||
|
{
|
||||||
|
uint ByteAsWord = bytesAsWords[ByteIt];
|
||||||
|
|
||||||
|
*Dest = (byte)((*Dest & DestMaskByte0) | (byte)(ByteAsWord << BitCountUsedInByte));
|
||||||
|
++Dest;
|
||||||
|
if (bStraddlesTwoBytes)
|
||||||
|
*Dest = (byte)((*Dest & DestMaskByte1) | (byte)(ByteAsWord >> BitCountLeftInByte));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteIntWrapped(uint value, uint valueMax)
|
||||||
|
{
|
||||||
|
var lengthBits = (int) Math.Ceiling(Math.Log2(valueMax));
|
||||||
|
|
||||||
|
if (AllowAppend(lengthBits))
|
||||||
|
{
|
||||||
|
uint newValue = 0;
|
||||||
|
|
||||||
|
for (uint mask = 1; newValue + mask < valueMax && (mask != 0); mask *= 2, Num++)
|
||||||
|
{
|
||||||
|
if ((value & mask) != 0)
|
||||||
|
{
|
||||||
|
Data[Num >> 3] += FBitUtil.GShift[Num & 7];
|
||||||
|
newValue += mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetOverflowed(lengthBits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteBit(bool value)
|
||||||
|
{
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
WriteBit(1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteBit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void WriteBit(byte value)
|
public void WriteBit(byte value)
|
||||||
{
|
{
|
||||||
if (AllowAppend(1))
|
if (AllowAppend(1))
|
||||||
{
|
{
|
||||||
if (value != 0)
|
if (value != 0)
|
||||||
{
|
{
|
||||||
Buffer[Num >> 3] |= FBitUtil.GShift[Num & 7];
|
Data[Num >> 3] |= FBitUtil.GShift[Num & 7];
|
||||||
}
|
}
|
||||||
|
|
||||||
Num++;
|
Num++;
|
||||||
@@ -83,7 +306,7 @@ public class FBitWriter : FArchive
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetOverflowed(long lengthBits)
|
protected void SetOverflowed(long lengthBits)
|
||||||
{
|
{
|
||||||
if (!AllowOverflow)
|
if (!AllowOverflow)
|
||||||
{
|
{
|
||||||
@@ -93,14 +316,32 @@ public class FBitWriter : FArchive
|
|||||||
SetError();
|
SetError();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool AllowAppend(long lengthBits)
|
public bool AllowAppend(long lengthBits)
|
||||||
{
|
{
|
||||||
if (Num + lengthBits > Max)
|
if (Num + lengthBits > Max)
|
||||||
{
|
{
|
||||||
if (AllowResize)
|
if (AllowResize)
|
||||||
|
{
|
||||||
|
// Resize our buffer. The common case for resizing bitwriters is hitting the max and continuing to add a lot of small segments of data
|
||||||
|
// Though we could just allow the TArray buffer to handle the slack and resizing, we would still constantly hit the FBitWriter's max
|
||||||
|
// and cause this block to be executed, as well as constantly zeroing out memory inside AddZeroes (though the memory would be allocated
|
||||||
|
// in chunks).
|
||||||
|
Max = Math.Max(Max << 1, Num + lengthBits);
|
||||||
|
var byteMax = (Max + 7) >> 3;
|
||||||
|
|
||||||
|
if (!_usesPool)
|
||||||
|
{
|
||||||
|
var dataTemp = Data;
|
||||||
|
Array.Resize(ref dataTemp, (int) byteMax);
|
||||||
|
Data = dataTemp;
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -110,28 +351,26 @@ public class FBitWriter : FArchive
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] GetData()
|
public void SetAllowResize(bool newResize)
|
||||||
{
|
{
|
||||||
if (IsError())
|
AllowResize = true;
|
||||||
{
|
|
||||||
Logger.Error("Retrieved data from a BitWriter that had an error");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Buffer;
|
public override void Reset()
|
||||||
|
{
|
||||||
|
base.Reset();
|
||||||
|
Num = 0;
|
||||||
|
Array.Clear(Data);
|
||||||
|
_arIsSaving = true;
|
||||||
|
_arIsPersistent = true;
|
||||||
|
_arIsNetArchive = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long GetNumBytes()
|
public override void Dispose()
|
||||||
{
|
{
|
||||||
return (Num + 7) >> 3;
|
if (_usesPool)
|
||||||
}
|
|
||||||
|
|
||||||
public long GetNumBits()
|
|
||||||
{
|
{
|
||||||
return Num;
|
Pool.Return(Data, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long GetMaxBits()
|
|
||||||
{
|
|
||||||
return Max;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
namespace Prospect.Unreal.Serialization;
|
||||||
|
|
||||||
|
public struct FBitWriterMark
|
||||||
|
{
|
||||||
|
private bool _overflowed;
|
||||||
|
private long _num;
|
||||||
|
|
||||||
|
public FBitWriterMark()
|
||||||
|
{
|
||||||
|
_overflowed = false;
|
||||||
|
_num = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FBitWriterMark(FBitWriter writer)
|
||||||
|
{
|
||||||
|
_overflowed = writer.IsError();
|
||||||
|
_num = writer.GetNumBits();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long GetPos()
|
||||||
|
{
|
||||||
|
return _num;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Pop(FBitReader reader)
|
||||||
|
{
|
||||||
|
reader.Pos = _num;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Init(FBitWriter writer)
|
||||||
|
{
|
||||||
|
_num = writer.Num;
|
||||||
|
_overflowed = writer.IsError();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_overflowed = false;
|
||||||
|
_num = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PopWithoutClear(FBitWriter writer)
|
||||||
|
{
|
||||||
|
writer.Num = _num;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,10 +9,10 @@ public class FNetBitReader : FBitReader
|
|||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public FNetBitReader(UPackageMapClient? inPackageMap, byte[]? src, int num) : base(src, num)
|
public FNetBitReader(UPackageMap? inPackageMap, byte[]? src, int num) : base(src, num)
|
||||||
{
|
{
|
||||||
PackageMap = inPackageMap;
|
PackageMap = inPackageMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UPackageMapClient? PackageMap { get; }
|
public UPackageMap? PackageMap { get; }
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
using Prospect.Unreal.Core;
|
||||||
|
using Prospect.Unreal.Core.Names;
|
||||||
|
using Prospect.Unreal.Net;
|
||||||
|
|
||||||
|
namespace Prospect.Unreal.Serialization;
|
||||||
|
|
||||||
|
public class FNetBitWriter : FBitWriter
|
||||||
|
{
|
||||||
|
public FNetBitWriter() : base(0)
|
||||||
|
{
|
||||||
|
PackageMap = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FNetBitWriter(long inMaxBits) : base(inMaxBits, true)
|
||||||
|
{
|
||||||
|
PackageMap = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FNetBitWriter(UPackageMap inPackageMap, long inMaxBits) : base(inMaxBits, true)
|
||||||
|
{
|
||||||
|
PackageMap = inPackageMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FNetBitWriter(FNetBitWriter writer) : base(writer)
|
||||||
|
{
|
||||||
|
PackageMap = writer.PackageMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UPackageMap? PackageMap { get; }
|
||||||
|
|
||||||
|
public virtual void WriteFName(FName name)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void WriteUObject(UObject obj)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: FSoftObjectPath
|
||||||
|
// TODO: FSoftObjectPtr
|
||||||
|
// TODO: FWeakObjectPtr
|
||||||
|
}
|
||||||
@@ -23,6 +23,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{168B6247
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Prospect.Unreal.Tests", "Prospect.Unreal.Tests\Prospect.Unreal.Tests.csproj", "{14DAE1A4-6F3F-43C0-AC17-9DCDE420AF34}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Prospect.Unreal.Tests", "Prospect.Unreal.Tests\Prospect.Unreal.Tests.csproj", "{14DAE1A4-6F3F-43C0-AC17-9DCDE420AF34}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Prospect.Unreal.Generator", "Prospect.Unreal.Generator\Prospect.Unreal.Generator.csproj", "{71E3E262-49C5-4B6C-9670-D0741CEDB63B}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|x64 = Debug|x64
|
Debug|x64 = Debug|x64
|
||||||
@@ -85,6 +87,14 @@ Global
|
|||||||
{14DAE1A4-6F3F-43C0-AC17-9DCDE420AF34}.Release|x64.Build.0 = Release|Any CPU
|
{14DAE1A4-6F3F-43C0-AC17-9DCDE420AF34}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{14DAE1A4-6F3F-43C0-AC17-9DCDE420AF34}.Release|x86.ActiveCfg = Release|Any CPU
|
{14DAE1A4-6F3F-43C0-AC17-9DCDE420AF34}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{14DAE1A4-6F3F-43C0-AC17-9DCDE420AF34}.Release|x86.Build.0 = Release|Any CPU
|
{14DAE1A4-6F3F-43C0-AC17-9DCDE420AF34}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{71E3E262-49C5-4B6C-9670-D0741CEDB63B}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{71E3E262-49C5-4B6C-9670-D0741CEDB63B}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{71E3E262-49C5-4B6C-9670-D0741CEDB63B}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{71E3E262-49C5-4B6C-9670-D0741CEDB63B}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{71E3E262-49C5-4B6C-9670-D0741CEDB63B}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{71E3E262-49C5-4B6C-9670-D0741CEDB63B}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{71E3E262-49C5-4B6C-9670-D0741CEDB63B}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{71E3E262-49C5-4B6C-9670-D0741CEDB63B}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -100,5 +110,6 @@ Global
|
|||||||
{DE064B5C-5E9A-44FB-8DB0-5E61DD8ED4F6} = {76364BAA-BC9D-4A92-AA2A-8914FAE22834}
|
{DE064B5C-5E9A-44FB-8DB0-5E61DD8ED4F6} = {76364BAA-BC9D-4A92-AA2A-8914FAE22834}
|
||||||
{380DD490-8F01-4025-91D7-74C289AA5B75} = {76364BAA-BC9D-4A92-AA2A-8914FAE22834}
|
{380DD490-8F01-4025-91D7-74C289AA5B75} = {76364BAA-BC9D-4A92-AA2A-8914FAE22834}
|
||||||
{14DAE1A4-6F3F-43C0-AC17-9DCDE420AF34} = {168B6247-2D33-4942-AB3F-C5041B3BDFEA}
|
{14DAE1A4-6F3F-43C0-AC17-9DCDE420AF34} = {168B6247-2D33-4942-AB3F-C5041B3BDFEA}
|
||||||
|
{71E3E262-49C5-4B6C-9670-D0741CEDB63B} = {76364BAA-BC9D-4A92-AA2A-8914FAE22834}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
Reference in New Issue
Block a user