Merge branch 'dev' of https://github.com/Regalis11/Barotrauma into unstable
This commit is contained in:
@@ -1,37 +1,35 @@
|
||||
using Barotrauma.Steam;
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
#warning TODO: turn this into INetSerializableStruct
|
||||
partial class BannedPlayer
|
||||
{
|
||||
public string Name;
|
||||
public string EndPoint; public bool IsRangeBan;
|
||||
public UInt64 SteamID;
|
||||
public string Reason;
|
||||
public DateTime? ExpirationTime;
|
||||
public UInt16 UniqueIdentifier;
|
||||
public readonly string Name;
|
||||
public readonly Either<Address, AccountId> AddressOrAccountId;
|
||||
|
||||
private void ParseEndPointAsSteamId()
|
||||
{
|
||||
ulong endPointAsSteamId = SteamManager.SteamIDStringToUInt64(EndPoint);
|
||||
if (endPointAsSteamId != 0 && SteamID == 0) { SteamID = endPointAsSteamId; }
|
||||
}
|
||||
public readonly string Reason;
|
||||
public DateTime? ExpirationTime;
|
||||
public readonly UInt32 UniqueIdentifier;
|
||||
}
|
||||
|
||||
partial class BanList
|
||||
{
|
||||
private readonly List<BannedPlayer> bannedPlayers;
|
||||
|
||||
public IReadOnlyList<BannedPlayer> BannedPlayers => bannedPlayers;
|
||||
|
||||
public IEnumerable<string> BannedNames
|
||||
{
|
||||
get { return bannedPlayers.Select(bp => bp.Name); }
|
||||
}
|
||||
|
||||
public IEnumerable<string> BannedEndPoints
|
||||
public IEnumerable<Either<Address, AccountId>> BannedAddresses
|
||||
{
|
||||
get { return bannedPlayers.Select(bp => bp.EndPoint).Where(endPoint => !string.IsNullOrEmpty(endPoint)); }
|
||||
get { return bannedPlayers.Select(bp => bp.AddressOrAccountId); }
|
||||
}
|
||||
|
||||
partial void InitProjectSpecific();
|
||||
@@ -42,19 +40,5 @@ namespace Barotrauma.Networking
|
||||
bannedPlayers = new List<BannedPlayer>();
|
||||
InitProjectSpecific();
|
||||
}
|
||||
|
||||
public static string ToRange(string ip)
|
||||
{
|
||||
if (SteamManager.SteamIDStringToUInt64(ip) != 0) { return ip; }
|
||||
for (int i = ip.Length - 1; i > 0; i--)
|
||||
{
|
||||
if (ip[i] == '.')
|
||||
{
|
||||
ip = ip.Substring(0, i) + ".x";
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace Barotrauma.Networking
|
||||
Task<int> readTask = readStream?.ReadAsync(readTempBytes, 0, readTempBytes.Length, readCancellationToken.Token);
|
||||
if (readTask is null) { return -1; }
|
||||
|
||||
TimeSpan timeOut = TimeSpan.FromMilliseconds(100);
|
||||
int timeOutMilliseconds = 100;
|
||||
for (int i = 0; i < 150; i++)
|
||||
{
|
||||
if (shutDown)
|
||||
@@ -106,12 +106,9 @@ namespace Barotrauma.Networking
|
||||
readCancellationToken?.Cancel();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// BUG workaround for crash when closing the server under .NET 6.0, not sure if this is the proper way to fix it but it prevents it from crashing the client. - Markus
|
||||
#if NET6_0
|
||||
try
|
||||
{
|
||||
if (readTask.IsCompleted || readTask.Wait(100, readCancellationToken.Token))
|
||||
if (readTask.IsCompleted || readTask.Wait(timeOutMilliseconds, readCancellationToken.Token))
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -120,12 +117,6 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
if (readTask.IsCompleted || readTask.Wait(timeOut))
|
||||
{
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (readTask.Status != TaskStatus.RanToCompletion)
|
||||
|
||||
@@ -11,16 +11,15 @@ namespace Barotrauma.Networking
|
||||
public string Name;
|
||||
public Identifier PreferredJob;
|
||||
public CharacterTeamType PreferredTeam;
|
||||
public UInt16 NameID;
|
||||
public UInt64 SteamID;
|
||||
public byte ID;
|
||||
public UInt16 CharacterID;
|
||||
public UInt16 NameId;
|
||||
public AccountInfo AccountInfo;
|
||||
public byte SessionId;
|
||||
public UInt16 CharacterId;
|
||||
public float Karma;
|
||||
public bool Muted;
|
||||
public bool InGame;
|
||||
public bool HasPermissions;
|
||||
public bool IsOwner;
|
||||
public bool AllowKicking;
|
||||
public bool IsDownloading;
|
||||
}
|
||||
|
||||
@@ -28,10 +27,23 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
public const int MaxNameLength = 32;
|
||||
|
||||
public string Name; public UInt16 NameID;
|
||||
public byte ID;
|
||||
public UInt64 SteamID;
|
||||
public UInt64 OwnerSteamID;
|
||||
public string Name; public UInt16 NameId;
|
||||
|
||||
/// <summary>
|
||||
/// An ID for this client for the current session.
|
||||
/// THIS IS NOT A PERSISTENT VALUE. DO NOT STORE THIS LONG-TERM.
|
||||
/// IT CANNOT BE USED TO IDENTIFY PLAYERS ACROSS SESSIONS.
|
||||
/// </summary>
|
||||
public readonly byte SessionId;
|
||||
|
||||
public AccountInfo AccountInfo;
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the account used to authenticate this session.
|
||||
/// This value can be used as a persistent value to identify
|
||||
/// players in the banlist and campaign saves.
|
||||
/// </summary>
|
||||
public Option<AccountId> AccountId => AccountInfo.AccountId;
|
||||
|
||||
public LanguageIdentifier Language;
|
||||
|
||||
@@ -90,14 +102,14 @@ namespace Barotrauma.Networking
|
||||
|
||||
public UInt16 CharacterID;
|
||||
|
||||
private Vector2 spectate_position;
|
||||
private Vector2 spectatePos;
|
||||
public Vector2? SpectatePos
|
||||
{
|
||||
get
|
||||
{
|
||||
if (character == null || character.IsDead)
|
||||
{
|
||||
return spectate_position;
|
||||
return spectatePos;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -107,7 +119,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
set
|
||||
{
|
||||
spectate_position = value.Value;
|
||||
spectatePos = value.Value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,8 +176,6 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
public bool HasSpawned; //has the client spawned as a character during the current round
|
||||
|
||||
private readonly List<Client> kickVoters;
|
||||
|
||||
public HashSet<Identifier> GivenAchievements = new HashSet<Identifier>();
|
||||
|
||||
public ClientPermissions Permissions = ClientPermissions.None;
|
||||
@@ -173,25 +183,12 @@ namespace Barotrauma.Networking
|
||||
|
||||
private readonly object[] votes;
|
||||
|
||||
public int KickVoteCount
|
||||
{
|
||||
get { return kickVoters.Count; }
|
||||
}
|
||||
|
||||
/*public Client(NetPeer server, string name, byte ID)
|
||||
: this(name, ID)
|
||||
{
|
||||
|
||||
}*/
|
||||
|
||||
partial void InitProjSpecific();
|
||||
partial void DisposeProjSpecific();
|
||||
public Client(string name, byte ID)
|
||||
public Client(string name, byte sessionId)
|
||||
{
|
||||
this.Name = name;
|
||||
this.ID = ID;
|
||||
|
||||
kickVoters = new List<Client>();
|
||||
this.SessionId = sessionId;
|
||||
|
||||
votes = new object[Enum.GetNames(typeof(VoteType)).Length];
|
||||
|
||||
@@ -207,57 +204,21 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
votes[(int)voteType] = value;
|
||||
}
|
||||
|
||||
public void ResetVotes()
|
||||
{
|
||||
for (int i = 0; i < votes.Length; i++)
|
||||
{
|
||||
votes[i] = null;
|
||||
}
|
||||
|
||||
kickVoters.Clear();
|
||||
}
|
||||
|
||||
public void AddKickVote(Client voter)
|
||||
{
|
||||
if (voter != null && !kickVoters.Contains(voter)) { kickVoters.Add(voter); }
|
||||
}
|
||||
|
||||
|
||||
public void RemoveKickVote(Client voter)
|
||||
{
|
||||
kickVoters.Remove(voter);
|
||||
}
|
||||
|
||||
public bool HasKickVoteFrom(Client voter)
|
||||
{
|
||||
return kickVoters.Contains(voter);
|
||||
}
|
||||
|
||||
public bool HasKickVoteFromID(int id)
|
||||
{
|
||||
return kickVoters.Any(k => k.ID == id);
|
||||
}
|
||||
|
||||
|
||||
public static void UpdateKickVotes(List<Client> connectedClients)
|
||||
{
|
||||
foreach (Client client in connectedClients)
|
||||
{
|
||||
client.kickVoters.RemoveAll(voter => !connectedClients.Contains(voter));
|
||||
}
|
||||
}
|
||||
|
||||
public bool SessionOrAccountIdMatches(string userId)
|
||||
=> (AccountId.IsSome() && Networking.AccountId.Parse(userId) == AccountId)
|
||||
|| (byte.TryParse(userId, out byte sessionId) && SessionId == sessionId);
|
||||
|
||||
public void WritePermissions(IWriteMessage msg)
|
||||
{
|
||||
msg.Write(ID);
|
||||
msg.WriteByte(SessionId);
|
||||
msg.WriteRangedInteger((int)Permissions, 0, (int)ClientPermissions.All);
|
||||
if (HasPermission(ClientPermissions.ConsoleCommands))
|
||||
{
|
||||
msg.Write((UInt16)PermittedConsoleCommands.Count);
|
||||
msg.WriteUInt16((UInt16)PermittedConsoleCommands.Count);
|
||||
foreach (DebugConsole.Command command in PermittedConsoleCommands)
|
||||
{
|
||||
msg.Write(command.names[0]);
|
||||
msg.WriteString(command.names[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,8 @@ namespace Barotrauma.Networking
|
||||
|
||||
class PermissionPreset
|
||||
{
|
||||
public static List<PermissionPreset> List = new List<PermissionPreset>();
|
||||
|
||||
public static readonly List<PermissionPreset> List = new List<PermissionPreset>();
|
||||
|
||||
public readonly LocalizedString Name;
|
||||
public readonly LocalizedString Description;
|
||||
public readonly ClientPermissions Permissions;
|
||||
@@ -87,9 +87,11 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
public bool MatchesPermissions(ClientPermissions permissions, HashSet<DebugConsole.Command> permittedConsoleCommands)
|
||||
public bool MatchesPermissions(ClientPermissions permissions, ISet<DebugConsole.Command> permittedConsoleCommands)
|
||||
{
|
||||
return permissions == this.Permissions && PermittedCommands.SequenceEqual(permittedConsoleCommands);
|
||||
return permissions == Permissions
|
||||
&& PermittedCommands.All(permittedConsoleCommands.Contains)
|
||||
&& permittedConsoleCommands.All(PermittedCommands.Contains);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Serialization;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Barotrauma
|
||||
/// Using the attribute on the struct will make all fields and properties serialized
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Struct | AttributeTargets.Property)]
|
||||
public class NetworkSerialize : Attribute
|
||||
public sealed class NetworkSerialize : Attribute
|
||||
{
|
||||
public int MaxValueInt = int.MaxValue;
|
||||
public int MinValueInt = int.MinValue;
|
||||
@@ -56,21 +56,37 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Static class that contains serialize and deserialize functions for different types used in <see cref="INetSerializableStruct"/>
|
||||
/// </summary>
|
||||
public static class NetSerializableProperties
|
||||
[SuppressMessage("ReSharper", "RedundantTypeArgumentsOfMethod")]
|
||||
static class NetSerializableProperties
|
||||
{
|
||||
public readonly struct ReadWriteBehavior
|
||||
public interface IReadWriteBehavior
|
||||
{
|
||||
public delegate dynamic? ReadDelegate(IReadMessage inc, Type type, NetworkSerialize attribute);
|
||||
public delegate object? ReadDelegate(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField);
|
||||
|
||||
public delegate void WriteDelegate(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg);
|
||||
public delegate void WriteDelegate(object? obj, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField);
|
||||
|
||||
public readonly ReadDelegate ReadAction;
|
||||
public readonly WriteDelegate WriteAction;
|
||||
public ReadDelegate ReadAction { get; }
|
||||
public WriteDelegate WriteAction { get; }
|
||||
}
|
||||
|
||||
public readonly struct ReadWriteBehavior<T> : IReadWriteBehavior
|
||||
{
|
||||
public delegate T ReadDelegate(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField);
|
||||
|
||||
public delegate void WriteDelegate(T obj, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField);
|
||||
|
||||
public IReadWriteBehavior.ReadDelegate ReadAction { get; }
|
||||
public IReadWriteBehavior.WriteDelegate WriteAction { get; }
|
||||
|
||||
public ReadDelegate ReadActionDirect { get; }
|
||||
public WriteDelegate WriteActionDirect { get; }
|
||||
|
||||
public ReadWriteBehavior(ReadDelegate readAction, WriteDelegate writeAction)
|
||||
{
|
||||
ReadAction = readAction;
|
||||
WriteAction = writeAction;
|
||||
ReadAction = (inc, attribute, bitField) => readAction(inc, attribute, bitField);
|
||||
WriteAction = (o, attribute, msg, bitField) => writeAction((T)o!, attribute, msg, bitField);
|
||||
ReadActionDirect = readAction;
|
||||
WriteActionDirect = writeAction;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,17 +96,18 @@ namespace Barotrauma
|
||||
|
||||
public delegate void SetValueDelegate(object? obj, object? value);
|
||||
|
||||
public readonly string Name;
|
||||
public readonly Type Type;
|
||||
public readonly ReadWriteBehavior Behavior;
|
||||
public readonly IReadWriteBehavior Behavior;
|
||||
public readonly NetworkSerialize Attribute;
|
||||
public readonly SetValueDelegate SetValue;
|
||||
public readonly GetValueDelegate GetValue;
|
||||
public readonly bool HasOwnAttribute;
|
||||
|
||||
public CachedReflectedVariable(MemberInfo info, ReadWriteBehavior behavior, Type baseClassType)
|
||||
public CachedReflectedVariable(MemberInfo info, IReadWriteBehavior behavior, Type baseClassType)
|
||||
{
|
||||
Behavior = behavior;
|
||||
|
||||
Name = info.Name;
|
||||
switch (info)
|
||||
{
|
||||
case PropertyInfo pi:
|
||||
@@ -126,343 +143,373 @@ namespace Barotrauma
|
||||
|
||||
private static readonly Dictionary<Type, ImmutableArray<CachedReflectedVariable>> CachedVariables = new Dictionary<Type, ImmutableArray<CachedReflectedVariable>>();
|
||||
|
||||
private static readonly ImmutableDictionary<Type, ReadWriteBehavior> TypeBehaviors = new Dictionary<Type, ReadWriteBehavior>
|
||||
{
|
||||
{ typeof(Boolean), new ReadWriteBehavior(ReadBoolean, WriteDynamic) },
|
||||
{ typeof(Byte), new ReadWriteBehavior(ReadByte, WriteDynamic) },
|
||||
{ typeof(UInt16), new ReadWriteBehavior(ReadUInt16, WriteDynamic) },
|
||||
{ typeof(Int16), new ReadWriteBehavior(ReadInt16, WriteDynamic) },
|
||||
{ typeof(UInt32), new ReadWriteBehavior(ReadUInt32, WriteDynamic) },
|
||||
{ typeof(Int32), new ReadWriteBehavior(ReadInt32, WriteInt32) },
|
||||
{ typeof(UInt64), new ReadWriteBehavior(ReadUInt64, WriteDynamic) },
|
||||
{ typeof(Int64), new ReadWriteBehavior(ReadInt64, WriteDynamic) },
|
||||
{ typeof(Single), new ReadWriteBehavior(ReadSingle, WriteSingle) },
|
||||
{ typeof(Double), new ReadWriteBehavior(ReadDouble, WriteDynamic) },
|
||||
{ typeof(String), new ReadWriteBehavior(ReadString, WriteDynamic) },
|
||||
{ typeof(Identifier), new ReadWriteBehavior(ReadIdentifier, WriteDynamic) },
|
||||
{ typeof(Color), new ReadWriteBehavior(ReadColor, WriteColor) },
|
||||
{ typeof(Vector2), new ReadWriteBehavior(ReadVector2, WriteVector2) }
|
||||
}.ToImmutableDictionary();
|
||||
private static readonly Dictionary<Type, IReadWriteBehavior> TypeBehaviors
|
||||
= new Dictionary<Type, IReadWriteBehavior>
|
||||
{
|
||||
{ typeof(Boolean), new ReadWriteBehavior<Boolean>(ReadBoolean, WriteBoolean) },
|
||||
{ typeof(Byte), new ReadWriteBehavior<Byte>(ReadByte, WriteByte) },
|
||||
{ typeof(UInt16), new ReadWriteBehavior<UInt16>(ReadUInt16, WriteUInt16) },
|
||||
{ typeof(Int16), new ReadWriteBehavior<Int16>(ReadInt16, WriteInt16) },
|
||||
{ typeof(UInt32), new ReadWriteBehavior<UInt32>(ReadUInt32, WriteUInt32) },
|
||||
{ typeof(Int32), new ReadWriteBehavior<Int32>(ReadInt32, WriteInt32) },
|
||||
{ typeof(UInt64), new ReadWriteBehavior<UInt64>(ReadUInt64, WriteUInt64) },
|
||||
{ typeof(Int64), new ReadWriteBehavior<Int64>(ReadInt64, WriteInt64) },
|
||||
{ typeof(Single), new ReadWriteBehavior<Single>(ReadSingle, WriteSingle) },
|
||||
{ typeof(Double), new ReadWriteBehavior<Double>(ReadDouble, WriteDouble) },
|
||||
{ typeof(String), new ReadWriteBehavior<String>(ReadString, WriteString) },
|
||||
{ typeof(Identifier), new ReadWriteBehavior<Identifier>(ReadIdentifier, WriteIdentifier) },
|
||||
{ typeof(AccountId), new ReadWriteBehavior<AccountId>(ReadAccountId, WriteAccountId) },
|
||||
{ typeof(Color), new ReadWriteBehavior<Color>(ReadColor, WriteColor) },
|
||||
{ typeof(Vector2), new ReadWriteBehavior<Vector2>(ReadVector2, WriteVector2) }
|
||||
};
|
||||
|
||||
private static readonly ImmutableDictionary<Predicate<Type>, ReadWriteBehavior> TypePredicates = new Dictionary<Predicate<Type>, ReadWriteBehavior>
|
||||
private static readonly ImmutableDictionary<Predicate<Type>, Func<Type, IReadWriteBehavior>> BehaviorFactories = new Dictionary<Predicate<Type>, Func<Type, IReadWriteBehavior>>
|
||||
{
|
||||
// Arrays
|
||||
{ type => typeof(Array).IsAssignableFrom(type.BaseType), new ReadWriteBehavior(ReadArray, WriteArray) },
|
||||
{ type => type.IsArray, CreateArrayBehavior },
|
||||
|
||||
// Nested INetSerializableStructs
|
||||
{ type => typeof(INetSerializableStruct).IsAssignableFrom(type), new ReadWriteBehavior(ReadINetSerializableStruct, WriteINetSerializableStruct) },
|
||||
{ type => typeof(INetSerializableStruct).IsAssignableFrom(type), CreateINetSerializableStructBehavior },
|
||||
|
||||
// Enums
|
||||
{ type => type.IsEnum, new ReadWriteBehavior(ReadEnum, WriteEnum) },
|
||||
{ type => type.IsEnum, CreateEnumBehavior },
|
||||
|
||||
// Nullable
|
||||
{ type => Nullable.GetUnderlyingType(type) != null, new ReadWriteBehavior(ReadNullable, WriteNullable) },
|
||||
{ type => Nullable.GetUnderlyingType(type) != null, CreateNullableStructBehavior },
|
||||
|
||||
// ImmutableArray
|
||||
{ type => IsOfGenericType(type, typeof(ImmutableArray<>)), CreateImmutableArrayBehavior },
|
||||
|
||||
// Option
|
||||
{ type => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Option<>), new ReadWriteBehavior(ReadOption, WriteOption) }
|
||||
{ type => IsOfGenericType(type, typeof(Option<>)), CreateOptionBehavior }
|
||||
}.ToImmutableDictionary();
|
||||
|
||||
private static readonly ReadWriteBehavior InvalidReadWriteBehavior = new ReadWriteBehavior(ReadInvalid, WriteInvalid);
|
||||
|
||||
private static readonly Dictionary<Type, MethodInfo> cachedSomeCreateMethods = new Dictionary<Type, MethodInfo>();
|
||||
private static readonly Dictionary<Type, MethodInfo> cachedNoneCreateMethod = new Dictionary<Type, MethodInfo>();
|
||||
|
||||
private static void WriteInvalid(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg) =>
|
||||
throw new SerializationException($"Type {obj?.GetType()} cannot be serialized. Did you forget to implement {nameof(INetSerializableStruct)}?");
|
||||
|
||||
private static dynamic ReadInvalid(IReadMessage inc, Type type, NetworkSerialize attribute) => throw new SerializationException($"Type {type} cannot be deserialized. Did you forget to implement {nameof(INetSerializableStruct)}?");
|
||||
|
||||
private static void WriteOption(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg)
|
||||
/// <param name="behaviorGenericParam">The type that the behavior handles</param>
|
||||
/// <param name="funcGenericParam">The type that will be used as the generic parameter for the read/write methods</param>
|
||||
/// <param name="readFunc">The read method.
|
||||
/// It must have a generic parameter.
|
||||
/// The return type must be such that if the generic parameter is replaced with funcGenericParam, you get behaviorGenericParam.</param>
|
||||
/// <param name="writeFunc">The write method. The first parameter's type must be the same as readFunc's return type.</param>
|
||||
/// <typeparam name="TDelegateBase">Ideally the least specific type possible, because it's replaced by behaviorGenericParam</typeparam>
|
||||
/// <returns>A ReadWriteBehavior<behaviorGenericParam></returns>
|
||||
private static IReadWriteBehavior CreateBehavior<TDelegateBase>(Type behaviorGenericParam,
|
||||
Type funcGenericParam,
|
||||
ReadWriteBehavior<TDelegateBase>.ReadDelegate readFunc,
|
||||
ReadWriteBehavior<TDelegateBase>.WriteDelegate writeFunc)
|
||||
{
|
||||
if (obj is null) { throw new ArgumentNullException(nameof(obj), "Tried to write 'null' into a non-nullable type"); }
|
||||
var behaviorType = typeof(ReadWriteBehavior<>).MakeGenericType(behaviorGenericParam);
|
||||
|
||||
Type type = obj.GetType();
|
||||
Type optionType = type.GetGenericTypeDefinition();
|
||||
Type underlyingType = type.GetGenericArguments()[0];
|
||||
var readDelegateType = typeof(ReadWriteBehavior<>.ReadDelegate).MakeGenericType(behaviorGenericParam);
|
||||
var writeDelegateType = typeof(ReadWriteBehavior<>.WriteDelegate).MakeGenericType(behaviorGenericParam);
|
||||
|
||||
if (optionType == typeof(None<>))
|
||||
var constructor = behaviorType.GetConstructor(new[]
|
||||
{
|
||||
msg.Write(false);
|
||||
readDelegateType, writeDelegateType
|
||||
});
|
||||
|
||||
return (constructor!.Invoke(new object[]
|
||||
{
|
||||
readFunc.Method.GetGenericMethodDefinition().MakeGenericMethod(funcGenericParam).CreateDelegate(readDelegateType),
|
||||
writeFunc.Method.GetGenericMethodDefinition().MakeGenericMethod(funcGenericParam).CreateDelegate(writeDelegateType)
|
||||
}) as IReadWriteBehavior)!;
|
||||
}
|
||||
|
||||
private static IReadWriteBehavior CreateArrayBehavior(Type arrayType) =>
|
||||
CreateBehavior(
|
||||
arrayType,
|
||||
arrayType.GetElementType()!,
|
||||
ReadArray<object>,
|
||||
WriteArray<object>);
|
||||
|
||||
private static IReadWriteBehavior CreateINetSerializableStructBehavior(Type structType) =>
|
||||
CreateBehavior(
|
||||
structType,
|
||||
structType,
|
||||
ReadINetSerializableStruct<INetSerializableStruct>,
|
||||
WriteINetSerializableStruct<INetSerializableStruct>);
|
||||
|
||||
private static IReadWriteBehavior CreateEnumBehavior(Type enumType) =>
|
||||
CreateBehavior(
|
||||
enumType,
|
||||
enumType,
|
||||
ReadEnum<Enum>,
|
||||
WriteEnum<Enum>);
|
||||
|
||||
private static IReadWriteBehavior CreateNullableStructBehavior(Type nullableType) =>
|
||||
CreateBehavior(
|
||||
nullableType,
|
||||
Nullable.GetUnderlyingType(nullableType)!,
|
||||
ReadNullable<int>,
|
||||
WriteNullable<int>);
|
||||
|
||||
private static IReadWriteBehavior CreateOptionBehavior(Type optionType) =>
|
||||
CreateBehavior(
|
||||
optionType,
|
||||
optionType.GetGenericArguments()[0],
|
||||
ReadOption<object>,
|
||||
WriteOption<object>);
|
||||
|
||||
private static IReadWriteBehavior CreateImmutableArrayBehavior(Type arrayType) =>
|
||||
CreateBehavior(
|
||||
arrayType,
|
||||
arrayType.GetGenericArguments()[0],
|
||||
ReadImmutableArray<object>,
|
||||
WriteImmutableArray<object>);
|
||||
|
||||
private static ImmutableArray<T> ReadImmutableArray<T>(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) where T : notnull
|
||||
{
|
||||
return ReadArray<T>(inc, attribute, bitField).ToImmutableArray();
|
||||
}
|
||||
|
||||
private static void WriteImmutableArray<T>(ImmutableArray<T> array, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) where T : notnull
|
||||
{
|
||||
ToolBox.ThrowIfNull(array);
|
||||
WriteIReadOnlyCollection<T>(array, attribute, msg, bitField);
|
||||
}
|
||||
|
||||
private static T[] ReadArray<T>(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) where T : notnull
|
||||
{
|
||||
int length = bitField.ReadInteger(0, attribute.ArrayMaxSize);
|
||||
|
||||
T[] array = new T[length];
|
||||
|
||||
if (!TryFindBehavior(out ReadWriteBehavior<T> behavior))
|
||||
{
|
||||
throw new InvalidOperationException($"Could not find suitable behavior for type {typeof(T)} in {nameof(ReadArray)}");
|
||||
}
|
||||
else if (optionType == typeof(Some<>))
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
msg.Write(true);
|
||||
if (TryFindBehavior(underlyingType, out ReadWriteBehavior behavior))
|
||||
{
|
||||
behavior.WriteAction(obj.Value, attribute, msg);
|
||||
}
|
||||
array[i] = behavior.ReadActionDirect(inc, attribute, bitField);
|
||||
}
|
||||
else
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
private static void WriteArray<T>(T[] array, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) where T : notnull
|
||||
{
|
||||
ToolBox.ThrowIfNull(array);
|
||||
WriteIReadOnlyCollection(array, attribute, msg, bitField);
|
||||
}
|
||||
|
||||
private static void WriteIReadOnlyCollection<T>(IReadOnlyCollection<T> array, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) where T : notnull
|
||||
{
|
||||
bitField.WriteInteger(array.Count, 0, attribute.ArrayMaxSize);
|
||||
|
||||
if (!TryFindBehavior(out ReadWriteBehavior<T> behavior))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(obj), "Option type was neither None or Some");
|
||||
throw new InvalidOperationException($"Could not find suitable behavior for type {typeof(T)} in {nameof(WriteArray)}");
|
||||
}
|
||||
|
||||
foreach (T o in array)
|
||||
{
|
||||
behavior.WriteActionDirect(o, attribute, msg, bitField);
|
||||
}
|
||||
}
|
||||
|
||||
private static dynamic? ReadOption(IReadMessage inc, Type type, NetworkSerialize attribute)
|
||||
private static T ReadINetSerializableStruct<T>(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) where T : INetSerializableStruct
|
||||
{
|
||||
Type underlyingType = type.GetGenericArguments()[0];
|
||||
bool hasValue = inc.ReadBoolean();
|
||||
if (!hasValue)
|
||||
{
|
||||
return GetCreateMethod(typeof(None<>), underlyingType, cachedNoneCreateMethod).Invoke(null, null);
|
||||
}
|
||||
|
||||
if (TryFindBehavior(underlyingType, out ReadWriteBehavior behavior))
|
||||
{
|
||||
dynamic? value = behavior.ReadAction(inc, underlyingType, attribute);
|
||||
return GetCreateMethod(typeof(Some<>), underlyingType, cachedSomeCreateMethods).Invoke(null, new[] { value });
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Could not find suitable behavior for type {underlyingType} in {nameof(ReadOption)}");
|
||||
|
||||
static MethodInfo GetCreateMethod(Type optionType, Type type, Dictionary<Type, MethodInfo> cache)
|
||||
{
|
||||
if (cache.TryGetValue(type, out MethodInfo? foundInfo))
|
||||
{
|
||||
return foundInfo;
|
||||
}
|
||||
|
||||
Type genericType = optionType.MakeGenericType(type);
|
||||
MethodInfo info = genericType.GetMethod("Create", BindingFlags.Static | BindingFlags.Public)!;
|
||||
cache.Add(type, info);
|
||||
return info;
|
||||
}
|
||||
return INetSerializableStruct.ReadInternal<T>(inc, bitField);
|
||||
}
|
||||
|
||||
private static void WriteNullable(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg)
|
||||
private static void WriteINetSerializableStruct<T>(T serializableStruct, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) where T : INetSerializableStruct
|
||||
{
|
||||
if (obj is { } notNull)
|
||||
{
|
||||
msg.Write(true);
|
||||
|
||||
if (TryFindBehavior(notNull.GetType(), out ReadWriteBehavior behavior))
|
||||
{
|
||||
// uh oh, something terrible has happened!
|
||||
if (behavior.WriteAction == WriteNullable) { behavior = InvalidReadWriteBehavior; }
|
||||
|
||||
behavior.WriteAction(notNull, attribute, msg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
msg.Write(false);
|
||||
ToolBox.ThrowIfNull(serializableStruct);
|
||||
serializableStruct.WriteInternal(msg, bitField);
|
||||
}
|
||||
|
||||
private static dynamic? ReadNullable(IReadMessage inc, Type type, NetworkSerialize attribute)
|
||||
private static T ReadEnum<T>(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) where T : Enum
|
||||
{
|
||||
if (!inc.ReadBoolean()) { return null; }
|
||||
var type = typeof(T);
|
||||
|
||||
Type? underlyingType = Nullable.GetUnderlyingType(type);
|
||||
if (underlyingType is null) { throw new InvalidOperationException($"Could not get the underlying type of {type} in {nameof(ReadNullable)}"); }
|
||||
|
||||
if (TryFindBehavior(underlyingType, out ReadWriteBehavior behavior))
|
||||
{
|
||||
// uh oh, something terrible has happened!
|
||||
if (behavior.ReadAction == ReadNullable) { behavior = InvalidReadWriteBehavior; }
|
||||
|
||||
return behavior.ReadAction(inc, underlyingType, attribute);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Could not find suitable behavior for type {underlyingType} in {nameof(ReadNullable)}");
|
||||
}
|
||||
|
||||
private static void WriteEnum(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg)
|
||||
{
|
||||
if (obj is null) { throw new ArgumentNullException(nameof(obj), "Tried to write 'null' into a non-nullable type"); }
|
||||
|
||||
Range<int> range = GetEnumRange(obj.GetType());
|
||||
msg.WriteRangedInteger(Convert.ChangeType(obj, obj.GetTypeCode()), range.Start, range.End);
|
||||
}
|
||||
|
||||
private static dynamic ReadEnum(IReadMessage inc, Type type, NetworkSerialize attribute)
|
||||
{
|
||||
Range<int> range = GetEnumRange(type);
|
||||
int enumIndex = inc.ReadRangedInteger(range.Start, range.End);
|
||||
int enumIndex = bitField.ReadInteger(range.Start, range.End);
|
||||
|
||||
foreach (dynamic? e in Enum.GetValues(type))
|
||||
if (typeof(T).GetCustomAttribute<FlagsAttribute>() != null)
|
||||
{
|
||||
if (Convert.ChangeType(e, e!.GetTypeCode()) == enumIndex) { return e; }
|
||||
return (T)(object)enumIndex;
|
||||
}
|
||||
|
||||
foreach (T e in (T[])Enum.GetValues(type))
|
||||
{
|
||||
if (((int)(object)e) == enumIndex) { return e; }
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"An enum {type} with value {enumIndex} could not be found in {nameof(ReadEnum)}");
|
||||
}
|
||||
|
||||
private static void WriteINetSerializableStruct(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg)
|
||||
private static void WriteEnum<T>(T value, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) where T : Enum
|
||||
{
|
||||
if (obj is null) { throw new ArgumentNullException(nameof(obj), "Tried to write 'null' into a non-nullable type"); }
|
||||
ToolBox.ThrowIfNull(value);
|
||||
|
||||
if (!(obj is INetSerializableStruct serializableStruct)) { throw new InvalidOperationException($"Object in {nameof(WriteINetSerializableStruct)} was {obj.GetType()} but expected {nameof(INetSerializableStruct)}"); }
|
||||
|
||||
serializableStruct.Write(msg);
|
||||
Range<int> range = GetEnumRange(typeof(T));
|
||||
bitField.WriteInteger((int)Convert.ChangeType(value, value.GetTypeCode()), range.Start, range.End);
|
||||
}
|
||||
|
||||
private static dynamic ReadINetSerializableStruct(IReadMessage inc, Type type, NetworkSerialize attribute)
|
||||
{
|
||||
return INetSerializableStruct.ReadDynamic(type, inc);
|
||||
}
|
||||
|
||||
private static void WriteDynamic(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg)
|
||||
{
|
||||
if (obj is null) { throw new ArgumentNullException(nameof(obj), "Tried to write 'null' into a non-nullable type"); }
|
||||
|
||||
msg.Write(obj);
|
||||
}
|
||||
|
||||
private static dynamic ReadArray(IReadMessage inc, Type type, NetworkSerialize attribute)
|
||||
{
|
||||
Type? elementType = type.GetElementType();
|
||||
if (elementType is null) { throw new InvalidOperationException($"Could not get the element type of {type} in {nameof(ReadArray)}"); }
|
||||
|
||||
int length = inc.ReadRangedInteger(0, attribute.ArrayMaxSize);
|
||||
|
||||
Array list = Array.CreateInstance(elementType, length);
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
private static T? ReadNullable<T>(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) where T : struct =>
|
||||
ReadOption<T>(inc, attribute, bitField) switch
|
||||
{
|
||||
if (TryFindBehavior(elementType, out ReadWriteBehavior behavior))
|
||||
{
|
||||
list.SetValue(behavior.ReadAction(inc, elementType, attribute), i);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException($"Could not find suitable behavior for type {elementType} in {nameof(ReadArray)}");
|
||||
}
|
||||
Some<T> { Value: var value } => value,
|
||||
None<T> _ => null,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
private static void WriteNullable<T>(T? value, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) where T : struct =>
|
||||
WriteOption<T>(value.HasValue ? Option<T>.Some(value.Value) : Option<T>.None(), attribute, msg, bitField);
|
||||
|
||||
private static Option<T> ReadOption<T>(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) where T : notnull
|
||||
{
|
||||
bool hasValue = bitField.ReadBoolean();
|
||||
if (!hasValue)
|
||||
{
|
||||
return Option<T>.None();
|
||||
}
|
||||
|
||||
return list;
|
||||
if (TryFindBehavior(out ReadWriteBehavior<T> behavior))
|
||||
{
|
||||
return Option<T>.Some(behavior.ReadActionDirect(inc, attribute, bitField));
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Could not find suitable behavior for type {typeof(T)} in {nameof(ReadOption)}");
|
||||
}
|
||||
|
||||
private static void WriteArray(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg)
|
||||
private static void WriteOption<T>(Option<T> option, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) where T : notnull
|
||||
{
|
||||
if (obj is null) { throw new ArgumentNullException(nameof(obj), "Tried to write 'null' into a non-nullable type"); }
|
||||
ToolBox.ThrowIfNull(option);
|
||||
|
||||
if (!(obj is Array array)) { throw new InvalidOperationException($"Object in {nameof(WriteArray)} was {obj.GetType()} but expected {nameof(Array)}"); }
|
||||
|
||||
msg.WriteRangedInteger(array.Length, 0, attribute.ArrayMaxSize);
|
||||
|
||||
foreach (dynamic? o in array)
|
||||
if (option.TryUnwrap(out T value))
|
||||
{
|
||||
if (TryFindBehavior(o!.GetType(), out ReadWriteBehavior behavior))
|
||||
bitField.WriteBoolean(true);
|
||||
if (TryFindBehavior(out ReadWriteBehavior<T> behavior))
|
||||
{
|
||||
behavior.WriteAction(o, attribute, msg);
|
||||
behavior.WriteActionDirect(value, attribute, msg, bitField);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bitField.WriteBoolean(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static dynamic ReadBoolean(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadBoolean();
|
||||
private static bool ReadBoolean(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => bitField.ReadBoolean();
|
||||
private static void WriteBoolean(bool b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { bitField.WriteBoolean(b); }
|
||||
|
||||
private static byte ReadByte(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadByte();
|
||||
private static void WriteByte(byte b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.WriteByte(b); }
|
||||
|
||||
private static dynamic ReadByte(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadByte();
|
||||
private static ushort ReadUInt16(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadUInt16();
|
||||
private static void WriteUInt16(ushort b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.WriteUInt16(b); }
|
||||
|
||||
private static dynamic ReadUInt16(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadUInt16();
|
||||
private static short ReadInt16(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadInt16();
|
||||
private static void WriteInt16(short b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.WriteInt16(b); }
|
||||
|
||||
private static dynamic ReadInt16(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadInt16();
|
||||
private static uint ReadUInt32(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadUInt32();
|
||||
private static void WriteUInt32(uint b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.WriteUInt32(b); }
|
||||
|
||||
private static dynamic ReadUInt32(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadUInt32();
|
||||
|
||||
private static dynamic ReadInt32(IReadMessage inc, Type type, NetworkSerialize attribute)
|
||||
private static int ReadInt32(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField)
|
||||
{
|
||||
if (IsRanged(attribute.MinValueInt, attribute.MaxValueInt))
|
||||
{
|
||||
return inc.ReadRangedInteger(attribute.MinValueInt, attribute.MaxValueInt);
|
||||
return bitField.ReadInteger(attribute.MinValueInt, attribute.MaxValueInt);
|
||||
}
|
||||
|
||||
return inc.ReadInt32();
|
||||
}
|
||||
|
||||
private static void WriteInt32(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg)
|
||||
private static void WriteInt32(int i, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField)
|
||||
{
|
||||
if (obj is null) { throw new ArgumentNullException(nameof(obj), "Tried to write 'null' into a non-nullable type"); }
|
||||
ToolBox.ThrowIfNull(i);
|
||||
|
||||
if (IsRanged(attribute.MinValueInt, attribute.MaxValueInt))
|
||||
{
|
||||
msg.WriteRangedInteger(obj, attribute.MinValueInt, attribute.MaxValueInt);
|
||||
bitField.WriteInteger(i, attribute.MinValueInt, attribute.MaxValueInt);
|
||||
return;
|
||||
}
|
||||
|
||||
msg.Write(obj);
|
||||
msg.WriteInt32(i);
|
||||
}
|
||||
|
||||
private static dynamic ReadUInt64(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadUInt64();
|
||||
private static ulong ReadUInt64(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadUInt64();
|
||||
private static void WriteUInt64(ulong b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.WriteUInt64(b); }
|
||||
|
||||
private static dynamic ReadInt64(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadInt64();
|
||||
private static long ReadInt64(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadInt64();
|
||||
private static void WriteInt64(long b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.WriteInt64(b); }
|
||||
|
||||
private static dynamic ReadSingle(IReadMessage inc, Type type, NetworkSerialize attribute)
|
||||
private static float ReadSingle(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField)
|
||||
{
|
||||
if (IsRanged(attribute.MinValueFloat, attribute.MaxValueFloat))
|
||||
{
|
||||
return inc.ReadRangedSingle(attribute.MinValueFloat, attribute.MaxValueFloat, attribute.NumberOfBits);
|
||||
return bitField.ReadFloat(attribute.MinValueFloat, attribute.MaxValueFloat, attribute.NumberOfBits);
|
||||
}
|
||||
|
||||
return inc.ReadSingle();
|
||||
}
|
||||
|
||||
private static void WriteSingle(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg)
|
||||
private static void WriteSingle(float f, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField)
|
||||
{
|
||||
if (obj is null) { throw new ArgumentNullException(nameof(obj), "Tried to write 'null' into a non-nullable type"); }
|
||||
ToolBox.ThrowIfNull(f);
|
||||
|
||||
if (IsRanged(attribute.MinValueFloat, attribute.MaxValueFloat))
|
||||
{
|
||||
msg.WriteRangedSingle(obj, attribute.MinValueFloat, attribute.MaxValueFloat, attribute.NumberOfBits);
|
||||
bitField.WriteFloat(f, attribute.MinValueFloat, attribute.MaxValueFloat, attribute.NumberOfBits);
|
||||
return;
|
||||
}
|
||||
|
||||
msg.Write(obj);
|
||||
msg.WriteSingle(f);
|
||||
}
|
||||
|
||||
private static dynamic ReadDouble(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadDouble();
|
||||
private static double ReadDouble(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadDouble();
|
||||
private static void WriteDouble(double b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.WriteDouble(b); }
|
||||
|
||||
private static dynamic ReadString(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadString();
|
||||
private static string ReadString(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadString();
|
||||
private static void WriteString(string b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.WriteString(b); }
|
||||
|
||||
private static dynamic ReadIdentifier(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadIdentifier();
|
||||
private static Identifier ReadIdentifier(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadIdentifier();
|
||||
private static void WriteIdentifier(Identifier b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.WriteIdentifier(b); }
|
||||
|
||||
private static dynamic ReadColor(IReadMessage inc, Type type, NetworkSerialize attribute) => attribute.IncludeColorAlpha ? inc.ReadColorR8G8B8A8() : inc.ReadColorR8G8B8();
|
||||
|
||||
private static void WriteColor(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg)
|
||||
private static AccountId ReadAccountId(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField)
|
||||
{
|
||||
if (obj is null) { throw new ArgumentNullException(nameof(obj), "Tried to write 'null' into a non-nullable type"); }
|
||||
string str = inc.ReadString();
|
||||
return AccountId.Parse(str).TryUnwrap(out var accountId)
|
||||
? accountId
|
||||
: throw new InvalidCastException($"Could not parse \"{str}\" as an {nameof(AccountId)}");
|
||||
}
|
||||
|
||||
private static void WriteAccountId(AccountId accountId, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField)
|
||||
{
|
||||
msg.WriteString(accountId.StringRepresentation);
|
||||
}
|
||||
|
||||
private static Color ReadColor(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => attribute.IncludeColorAlpha ? inc.ReadColorR8G8B8A8() : inc.ReadColorR8G8B8();
|
||||
|
||||
private static void WriteColor(Color color, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField)
|
||||
{
|
||||
ToolBox.ThrowIfNull(color);
|
||||
|
||||
if (attribute.IncludeColorAlpha)
|
||||
{
|
||||
msg.WriteColorR8G8B8A8(obj);
|
||||
msg.WriteColorR8G8B8A8(color);
|
||||
return;
|
||||
}
|
||||
|
||||
msg.WriteColorR8G8B8(obj);
|
||||
msg.WriteColorR8G8B8(color);
|
||||
}
|
||||
|
||||
private static dynamic ReadVector2(IReadMessage inc, Type type, NetworkSerialize attribute)
|
||||
private static Vector2 ReadVector2(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField)
|
||||
{
|
||||
float x;
|
||||
float y;
|
||||
|
||||
if (IsRanged(attribute.MinValueFloat, attribute.MaxValueFloat))
|
||||
{
|
||||
x = inc.ReadRangedSingle(attribute.MinValueFloat, attribute.MaxValueFloat, attribute.NumberOfBits);
|
||||
y = inc.ReadRangedSingle(attribute.MinValueFloat, attribute.MaxValueFloat, attribute.NumberOfBits);
|
||||
}
|
||||
else
|
||||
{
|
||||
x = inc.ReadSingle();
|
||||
y = inc.ReadSingle();
|
||||
}
|
||||
float x = ReadSingle(inc, attribute, bitField);
|
||||
float y = ReadSingle(inc, attribute, bitField);
|
||||
|
||||
return new Vector2(x, y);
|
||||
}
|
||||
|
||||
private static void WriteVector2(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg)
|
||||
private static void WriteVector2(Vector2 vector2, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField)
|
||||
{
|
||||
if (obj is null) { throw new ArgumentNullException(nameof(obj), "Tried to write 'null' into a non-nullable type"); }
|
||||
ToolBox.ThrowIfNull(vector2);
|
||||
|
||||
var (x, y) = (Vector2)obj;
|
||||
if (IsRanged(attribute.MinValueFloat, attribute.MaxValueFloat))
|
||||
{
|
||||
msg.WriteRangedSingle(x, attribute.MinValueFloat, attribute.MaxValueFloat, attribute.NumberOfBits);
|
||||
msg.WriteRangedSingle(y, attribute.MinValueFloat, attribute.MaxValueFloat, attribute.NumberOfBits);
|
||||
return;
|
||||
}
|
||||
|
||||
msg.Write(x);
|
||||
msg.Write(y);
|
||||
var (x, y) = vector2;
|
||||
WriteSingle(x, attribute, msg, bitField);
|
||||
WriteSingle(y, attribute, msg, bitField);
|
||||
}
|
||||
|
||||
private static bool IsRanged(float minValue, float maxValue) => minValue > float.MinValue || maxValue < float.MaxValue;
|
||||
@@ -474,53 +521,71 @@ namespace Barotrauma
|
||||
return new Range<int>(values.Min(), values.Max());
|
||||
}
|
||||
|
||||
private static bool TryFindBehavior(Type type, out ReadWriteBehavior behavior)
|
||||
private static bool TryFindBehavior<T>(out ReadWriteBehavior<T> behavior) where T : notnull
|
||||
{
|
||||
if (TypeBehaviors.TryGetValue(type, out behavior)) { return true; }
|
||||
bool found = TryFindBehavior(typeof(T), out var bhvr);
|
||||
behavior = found ? (ReadWriteBehavior<T>)bhvr : default;
|
||||
return found;
|
||||
}
|
||||
|
||||
foreach (var (predicate, behavior2) in TypePredicates)
|
||||
private static bool TryFindBehavior(Type type, out IReadWriteBehavior behavior)
|
||||
{
|
||||
if (TypeBehaviors.TryGetValue(type, out var outBehavior))
|
||||
{
|
||||
if (predicate(type))
|
||||
{
|
||||
behavior = behavior2;
|
||||
return true;
|
||||
}
|
||||
behavior = outBehavior;
|
||||
return true;
|
||||
}
|
||||
|
||||
behavior = InvalidReadWriteBehavior;
|
||||
foreach (var (predicate, factory) in BehaviorFactories)
|
||||
{
|
||||
if (!predicate(type)) { continue; }
|
||||
|
||||
behavior = factory(type);
|
||||
TypeBehaviors.Add(type, behavior);
|
||||
return true;
|
||||
}
|
||||
|
||||
behavior = default!;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static ImmutableArray<CachedReflectedVariable> GetPropertiesAndFields(Type type, Type baseClassType)
|
||||
public static ImmutableArray<CachedReflectedVariable> GetPropertiesAndFields(Type type)
|
||||
{
|
||||
if (CachedVariables.TryGetValue(type, out var cached)) { return cached; }
|
||||
|
||||
List<CachedReflectedVariable> variables = new List<CachedReflectedVariable>();
|
||||
|
||||
IEnumerable<PropertyInfo> propertyInfos = type.GetProperties().Where(HasAttribute);
|
||||
IEnumerable<FieldInfo> fieldInfos = type.GetFields().Where(HasAttribute);
|
||||
IEnumerable<PropertyInfo> propertyInfos = type.GetProperties().Where(HasAttribute).Where(NotStatic);
|
||||
IEnumerable<FieldInfo> fieldInfos = type.GetFields().Where(HasAttribute).Where(NotStatic);
|
||||
|
||||
foreach (PropertyInfo info in propertyInfos)
|
||||
{
|
||||
if (TryFindBehavior(info.PropertyType, out ReadWriteBehavior behavior))
|
||||
if (info.SetMethod is null)
|
||||
{
|
||||
variables.Add(new CachedReflectedVariable(info, behavior, baseClassType));
|
||||
//skip get-only properties, because it's
|
||||
//useful to have them but their value
|
||||
//cannot be set when reading a struct
|
||||
continue;
|
||||
}
|
||||
if (TryFindBehavior(info.PropertyType, out IReadWriteBehavior behavior))
|
||||
{
|
||||
variables.Add(new CachedReflectedVariable(info, behavior, type));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new SerializationException($"Unable to serialize type \"{type}\".");
|
||||
throw new Exception($"Unable to serialize type \"{type}\".");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (FieldInfo info in fieldInfos)
|
||||
{
|
||||
if (TryFindBehavior(info.FieldType, out ReadWriteBehavior behavior))
|
||||
if (TryFindBehavior(info.FieldType, out IReadWriteBehavior behavior))
|
||||
{
|
||||
variables.Add(new CachedReflectedVariable(info, behavior, baseClassType));
|
||||
variables.Add(new CachedReflectedVariable(info, behavior, type));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new SerializationException($"Unable to serialize type \"{type}\".");
|
||||
throw new Exception($"Unable to serialize type \"{type}\".");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -528,7 +593,20 @@ namespace Barotrauma
|
||||
CachedVariables.Add(type, array);
|
||||
return array;
|
||||
|
||||
bool HasAttribute(MemberInfo info) => (info.GetCustomAttribute<NetworkSerialize>() ?? baseClassType.GetCustomAttribute<NetworkSerialize>()) != null;
|
||||
bool HasAttribute(MemberInfo info) => (info.GetCustomAttribute<NetworkSerialize>() ?? type.GetCustomAttribute<NetworkSerialize>()) != null;
|
||||
|
||||
static bool NotStatic(MemberInfo info)
|
||||
=> info switch
|
||||
{
|
||||
PropertyInfo property => property.GetGetMethod() is { IsStatic: false },
|
||||
FieldInfo field => !field.IsStatic,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
private static bool IsOfGenericType(Type type, Type comparedTo)
|
||||
{
|
||||
return type.IsGenericType && type.GetGenericTypeDefinition() == comparedTo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -575,13 +653,15 @@ namespace Barotrauma
|
||||
/// <see cref="Single">float</see><br/>
|
||||
/// <see cref="Double">double</see><br/>
|
||||
/// <see cref="String">string</see><br/>
|
||||
/// <see cref="Barotrauma.Networking.AccountId"/><br/>
|
||||
/// <see cref="System.Collections.Immutable.ImmutableArray{T}"></see><br/>
|
||||
/// <see cref="Microsoft.Xna.Framework.Color"/><br/>
|
||||
/// <see cref="Microsoft.Xna.Framework.Vector2"/><br/>
|
||||
/// In addition arrays, enums, <see cref="Nullable{T}"/> and <see cref="Option{T}"/> are supported.<br/>
|
||||
/// Using <see cref="Nullable{T}"/> or <see cref="Option{T}"/> will make the field or property optional.
|
||||
/// </remarks>
|
||||
/// <seealso cref="NetworkSerialize"/>
|
||||
public interface INetSerializableStruct
|
||||
internal interface INetSerializableStruct
|
||||
{
|
||||
/// <summary>
|
||||
/// Deserializes a network message into a struct.
|
||||
@@ -608,21 +688,34 @@ namespace Barotrauma
|
||||
/// <param name="inc">Incoming network message</param>
|
||||
/// <typeparam name="T">Type of the struct that implements <see cref="INetSerializableStruct"/></typeparam>
|
||||
/// <returns>A new struct of type T with fields and properties deserialized</returns>
|
||||
public static T Read<T>(IReadMessage inc) where T : INetSerializableStruct => (T)ReadDynamic(typeof(T), inc);
|
||||
|
||||
public static dynamic ReadDynamic(Type type, IReadMessage inc)
|
||||
public static T Read<T>(IReadMessage inc) where T : INetSerializableStruct
|
||||
{
|
||||
object? newObject = Activator.CreateInstance(type);
|
||||
IReadableBitField bitField = new ReadOnlyBitField(inc);
|
||||
return ReadInternal<T>(inc, bitField);
|
||||
}
|
||||
|
||||
public static T ReadInternal<T>(IReadMessage inc, IReadableBitField bitField) where T : INetSerializableStruct
|
||||
{
|
||||
object? newObject = Activator.CreateInstance(typeof(T));
|
||||
if (newObject is null) { return default!; }
|
||||
|
||||
var properties = NetSerializableProperties.GetPropertiesAndFields(type, type);
|
||||
var properties = NetSerializableProperties.GetPropertiesAndFields(typeof(T));
|
||||
foreach (NetSerializableProperties.CachedReflectedVariable property in properties)
|
||||
{
|
||||
NetworkSerialize attribute = property.Attribute;
|
||||
property.SetValue(newObject, property.Behavior.ReadAction(inc, property.Type, attribute));
|
||||
object? value = property.Behavior.ReadAction(inc, property.Attribute, bitField);
|
||||
try
|
||||
{
|
||||
property.SetValue(newObject, value);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw new Exception($"Failed to assign" +
|
||||
$" {value ?? "[NULL]"} ({value?.GetType().Name ?? "[NULL]"})" +
|
||||
$" to {typeof(T).Name}.{property.Name} ({property.Type.Name})", exception);
|
||||
}
|
||||
}
|
||||
|
||||
return newObject;
|
||||
return (T)newObject;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -651,34 +744,22 @@ namespace Barotrauma
|
||||
/// <param name="msg">Outgoing network message</param>
|
||||
public void Write(IWriteMessage msg)
|
||||
{
|
||||
Type type = GetType();
|
||||
var properties = NetSerializableProperties.GetPropertiesAndFields(type, type);
|
||||
IWritableBitField bitField = new WriteOnlyBitField();
|
||||
IWriteMessage structWriteMsg = new WriteOnlyMessage();
|
||||
WriteInternal(structWriteMsg, bitField);
|
||||
bitField.WriteToMessage(msg);
|
||||
msg.WriteBytes(structWriteMsg.Buffer, 0, structWriteMsg.LengthBytes);
|
||||
}
|
||||
|
||||
public void WriteInternal(IWriteMessage msg, IWritableBitField bitField)
|
||||
{
|
||||
var properties = NetSerializableProperties.GetPropertiesAndFields(GetType());
|
||||
|
||||
foreach (NetSerializableProperties.CachedReflectedVariable property in properties)
|
||||
{
|
||||
NetworkSerialize attribute = property.Attribute;
|
||||
property.Behavior.WriteAction(property.GetValue(this), attribute, msg);
|
||||
object? value = property.GetValue(this);
|
||||
property.Behavior.WriteAction(value!, property.Attribute, msg, bitField);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class WriteOnlyMessageExtensions
|
||||
{
|
||||
#if CLIENT
|
||||
public static IWriteMessage WithHeader(this IWriteMessage msg, ClientPacketHeader header)
|
||||
{
|
||||
msg.Write((byte)header);
|
||||
return msg;
|
||||
}
|
||||
#elif SERVER
|
||||
public static IWriteMessage WithHeader(this IWriteMessage msg, ServerPacketHeader header)
|
||||
{
|
||||
msg.Write((byte)header);
|
||||
return msg;
|
||||
}
|
||||
#endif
|
||||
public static void Write(this IWriteMessage msg, INetSerializableStruct serializableStruct)
|
||||
{
|
||||
serializableStruct.Write(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
static class NetBufferExtensions
|
||||
{
|
||||
//public static void WriteEnum(this NetBuffer buffer, Enum value)
|
||||
//{
|
||||
// buffer.WriteRangedInteger(0, Enum.GetValues(value.GetType()).Length - 1, Convert.ToInt32(value));
|
||||
//}
|
||||
|
||||
//public static TEnum ReadEnum<TEnum>(this NetBuffer buffer)
|
||||
//{
|
||||
// return (TEnum)(object)buffer.ReadRangedInteger(0, Enum.GetValues(typeof(TEnum)).Length - 1);
|
||||
//}
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -37,7 +37,7 @@ namespace Barotrauma.Networking
|
||||
//write an empty event to avoid messing up IDs
|
||||
//(otherwise the clients might read the next event in the message and think its ID
|
||||
//is consecutive to the previous one, even though we skipped over this broken event)
|
||||
tempBuffer.Write(Entity.NullEntityID);
|
||||
tempBuffer.WriteUInt16(Entity.NullEntityID);
|
||||
eventCount++;
|
||||
continue;
|
||||
}
|
||||
@@ -49,9 +49,9 @@ namespace Barotrauma.Networking
|
||||
break;
|
||||
}
|
||||
|
||||
tempBuffer.Write(e.EntityID);
|
||||
tempBuffer.WriteUInt16(e.EntityID);
|
||||
tempBuffer.WriteVariableUInt32((uint)tempEventBuffer.LengthBytes);
|
||||
tempBuffer.Write(tempEventBuffer.Buffer, 0, tempEventBuffer.LengthBytes);
|
||||
tempBuffer.WriteBytes(tempEventBuffer.Buffer, 0, tempEventBuffer.LengthBytes);
|
||||
sentEvents.Add(e);
|
||||
|
||||
eventCount++;
|
||||
@@ -60,9 +60,9 @@ namespace Barotrauma.Networking
|
||||
if (eventCount > 0)
|
||||
{
|
||||
msg.WritePadBits();
|
||||
msg.Write(eventsToSync[0].ID);
|
||||
msg.Write((byte)eventCount);
|
||||
msg.Write(tempBuffer.Buffer, 0, tempBuffer.LengthBytes);
|
||||
msg.WriteUInt16(eventsToSync[0].ID);
|
||||
msg.WriteByte((byte)eventCount);
|
||||
msg.WriteBytes(tempBuffer.Buffer, 0, tempBuffer.LengthBytes);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,19 @@ namespace Barotrauma.Networking
|
||||
public static bool IdMoreRecentOrMatches(ushort newId, ushort oldId)
|
||||
=> !IdMoreRecent(oldId, newId);
|
||||
|
||||
/// <summary>
|
||||
/// Returns some ID that is older than the input ID. There are no guarantees
|
||||
/// regarding its relation to values other than the input.
|
||||
/// </summary>
|
||||
public static ushort GetIdOlderThan(ushort id)
|
||||
#if DEBUG
|
||||
// Debug implementation has some RNG to discourage bad assumptions about the return value
|
||||
=> unchecked((ushort)(id - 1 - Rand.Int(500, sync: Rand.RandSync.Unsynced)));
|
||||
#else
|
||||
// Release implementation favors performance
|
||||
=> unchecked((ushort)(id - 1));
|
||||
#endif
|
||||
|
||||
public static ushort Difference(ushort id1, ushort id2)
|
||||
{
|
||||
int diff = id2 > id1 ? id2 - id1 : id1 - id2;
|
||||
|
||||
@@ -135,29 +135,29 @@ namespace Barotrauma.Networking
|
||||
|
||||
enum DisconnectReason
|
||||
{
|
||||
//do not attempt reconnecting with these reasons
|
||||
Unknown,
|
||||
Disconnected,
|
||||
Banned,
|
||||
Kicked,
|
||||
ServerShutdown,
|
||||
ServerCrashed,
|
||||
ServerFull,
|
||||
AuthenticationRequired,
|
||||
SteamAuthenticationRequired,
|
||||
SteamAuthenticationFailed,
|
||||
SessionTaken,
|
||||
TooManyFailedLogins,
|
||||
NoName,
|
||||
InvalidName,
|
||||
NameTaken,
|
||||
InvalidVersion,
|
||||
MissingContentPackage,
|
||||
IncompatibleContentPackage,
|
||||
NotOnWhitelist,
|
||||
SteamP2PError,
|
||||
|
||||
//attempt reconnecting with these reasons
|
||||
Timeout,
|
||||
ExcessiveDesyncOldEvent,
|
||||
ExcessiveDesyncRemovedEvent,
|
||||
SyncTimeout,
|
||||
SteamP2PError,
|
||||
SteamP2PTimeOut,
|
||||
SteamP2PTimeOut
|
||||
}
|
||||
|
||||
abstract partial class NetworkMember
|
||||
@@ -168,74 +168,38 @@ namespace Barotrauma.Networking
|
||||
set;
|
||||
}
|
||||
|
||||
public virtual bool IsServer
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
public abstract bool IsServer { get; }
|
||||
|
||||
public virtual bool IsClient
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
public abstract bool IsClient { get; }
|
||||
|
||||
public abstract void CreateEntityEvent(INetSerializable entity, NetEntityEvent.IData extraData = null);
|
||||
|
||||
#if DEBUG
|
||||
public Dictionary<string, long> messageCount = new Dictionary<string, long>();
|
||||
#endif
|
||||
|
||||
protected ServerSettings serverSettings;
|
||||
public abstract Voting Voting { get; }
|
||||
|
||||
public Voting Voting { get; protected set; }
|
||||
|
||||
protected TimeSpan updateInterval;
|
||||
protected DateTime updateTimer;
|
||||
|
||||
protected bool gameStarted;
|
||||
|
||||
protected RespawnManager respawnManager;
|
||||
|
||||
public bool ShowNetStats;
|
||||
|
||||
public float SimulatedRandomLatency, SimulatedMinimumLatency;
|
||||
public float SimulatedLoss;
|
||||
public float SimulatedDuplicatesChance;
|
||||
|
||||
public int TickRate
|
||||
{
|
||||
get { return serverSettings.TickRate; }
|
||||
set
|
||||
{
|
||||
serverSettings.TickRate = MathHelper.Clamp(value, 1, 60);
|
||||
updateInterval = new TimeSpan(0, 0, 0, 0, MathHelper.Clamp(1000 / serverSettings.TickRate, 1, 500));
|
||||
}
|
||||
}
|
||||
|
||||
public KarmaManager KarmaManager
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new KarmaManager();
|
||||
|
||||
public bool GameStarted
|
||||
{
|
||||
get { return gameStarted; }
|
||||
}
|
||||
public bool GameStarted { get; protected set; }
|
||||
|
||||
public virtual List<Client> ConnectedClients
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
public abstract IReadOnlyList<Client> ConnectedClients { get; }
|
||||
|
||||
public RespawnManager RespawnManager
|
||||
{
|
||||
get { return respawnManager; }
|
||||
}
|
||||
public RespawnManager RespawnManager { get; protected set; }
|
||||
|
||||
public ServerSettings ServerSettings { get; protected set; }
|
||||
|
||||
public TimeSpan UpdateInterval => new TimeSpan(0, 0, 0, 0, MathHelper.Clamp(1000 / ServerSettings.TickRate, 1, 500));
|
||||
|
||||
public ServerSettings ServerSettings
|
||||
{
|
||||
get { return serverSettings; }
|
||||
}
|
||||
|
||||
public bool CanUseRadio(Character sender)
|
||||
{
|
||||
@@ -272,33 +236,18 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
retVal += "color:#ff9900;";
|
||||
}
|
||||
retVal += "metadata:" + (client.SteamID != 0 ? client.SteamID.ToString() : client.ID.ToString()) + "‖" + (name ?? client.Name).Replace("‖", "") + "‖end‖";
|
||||
retVal += "metadata:" + (client.AccountId.TryUnwrap(out var accountId) ? accountId.ToString() : client.SessionId.ToString())
|
||||
+ "‖" + (name ?? client.Name).Replace("‖", "") + "‖end‖";
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public virtual void KickPlayer(string kickedName, string reason) { }
|
||||
public abstract void KickPlayer(string kickedName, string reason);
|
||||
|
||||
public virtual void BanPlayer(string kickedName, string reason, bool range = false, TimeSpan? duration = null) { }
|
||||
public abstract void BanPlayer(string kickedName, string reason, TimeSpan? duration = null);
|
||||
|
||||
public virtual void UnbanPlayer(string playerName, string playerIP) { }
|
||||
|
||||
public virtual void Update(float deltaTime) { }
|
||||
|
||||
public virtual void Disconnect() { }
|
||||
|
||||
/// <summary>
|
||||
/// Check if the two version are compatible (= if they can play together in multiplayer).
|
||||
/// Returns null if compatibility could not be determined (invalid/unknown version number).
|
||||
/// </summary>
|
||||
public static bool? IsCompatible(string myVersion, string remoteVersion)
|
||||
{
|
||||
if (string.IsNullOrEmpty(myVersion) || string.IsNullOrEmpty(remoteVersion)) { return null; }
|
||||
|
||||
if (!Version.TryParse(myVersion, out Version myVersionNumber)) { return null; }
|
||||
if (!Version.TryParse(remoteVersion, out Version remoteVersionNumber)) { return null; }
|
||||
|
||||
return IsCompatible(myVersionNumber, remoteVersionNumber);
|
||||
}
|
||||
public abstract void UnbanPlayer(string playerName);
|
||||
|
||||
public abstract void UnbanPlayer(Endpoint endpoint);
|
||||
|
||||
/// <summary>
|
||||
/// Check if the two version are compatible (= if they can play together in multiplayer).
|
||||
|
||||
@@ -47,32 +47,32 @@ namespace Barotrauma.Networking
|
||||
|
||||
public static void WriteOrder(IWriteMessage msg, Order order, Character targetCharacter, bool isNewOrder)
|
||||
{
|
||||
msg.Write(order.Prefab.Identifier);
|
||||
msg.Write(targetCharacter == null ? (UInt16)0 : targetCharacter.ID);
|
||||
msg.Write(order.TargetSpatialEntity is Entity ? (order.TargetEntity as Entity).ID : (UInt16)0);
|
||||
msg.WriteIdentifier(order.Prefab.Identifier);
|
||||
msg.WriteUInt16(targetCharacter == null ? (UInt16)0 : targetCharacter.ID);
|
||||
msg.WriteUInt16(order.TargetSpatialEntity is Entity ? (order.TargetEntity as Entity).ID : (UInt16)0);
|
||||
|
||||
// The option of a Dismiss order is written differently so we know what order we target
|
||||
// now that the game supports multiple current orders simultaneously
|
||||
if (!order.IsDismissal)
|
||||
{
|
||||
msg.Write((byte)order.Options.IndexOf(order.Option));
|
||||
msg.WriteByte((byte)order.Options.IndexOf(order.Option));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (order.Option != Identifier.Empty)
|
||||
{
|
||||
msg.Write(true);
|
||||
msg.WriteBoolean(true);
|
||||
string[] dismissedOrder = order.Option.Value.Split('.');
|
||||
msg.Write((byte)dismissedOrder.Length);
|
||||
msg.WriteByte((byte)dismissedOrder.Length);
|
||||
if (dismissedOrder.Length > 0)
|
||||
{
|
||||
Identifier dismissedOrderIdentifier = dismissedOrder[0].ToIdentifier();
|
||||
var orderPrefab = OrderPrefab.Prefabs[dismissedOrderIdentifier];
|
||||
msg.Write(dismissedOrderIdentifier);
|
||||
msg.WriteIdentifier(dismissedOrderIdentifier);
|
||||
if (dismissedOrder.Length > 1)
|
||||
{
|
||||
Identifier dismissedOrderOption = dismissedOrder[1].ToIdentifier();
|
||||
msg.Write((byte)orderPrefab.Options.IndexOf(dismissedOrderOption));
|
||||
msg.WriteByte((byte)orderPrefab.Options.IndexOf(dismissedOrderOption));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,29 +80,29 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
// If the order option is not specified for a Dismiss order,
|
||||
// we dismiss all current orders for the character
|
||||
msg.Write(false);
|
||||
msg.WriteBoolean(false);
|
||||
}
|
||||
}
|
||||
|
||||
msg.Write((byte)order.ManualPriority);
|
||||
msg.Write((byte)order.TargetType);
|
||||
msg.WriteByte((byte)order.ManualPriority);
|
||||
msg.WriteByte((byte)order.TargetType);
|
||||
if (order.TargetType == Order.OrderTargetType.Position && order.TargetSpatialEntity is OrderTarget orderTarget)
|
||||
{
|
||||
msg.Write(true);
|
||||
msg.Write(orderTarget.Position.X);
|
||||
msg.Write(orderTarget.Position.Y);
|
||||
msg.Write(orderTarget.Hull == null ? (UInt16)0 : orderTarget.Hull.ID);
|
||||
msg.WriteBoolean(true);
|
||||
msg.WriteSingle(orderTarget.Position.X);
|
||||
msg.WriteSingle(orderTarget.Position.Y);
|
||||
msg.WriteUInt16(orderTarget.Hull == null ? (UInt16)0 : orderTarget.Hull.ID);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write(false);
|
||||
msg.WriteBoolean(false);
|
||||
if (order.TargetType == Order.OrderTargetType.WallSection)
|
||||
{
|
||||
msg.Write((byte)(order.WallSectionIndex ?? 0));
|
||||
msg.WriteByte((byte)(order.WallSectionIndex ?? 0));
|
||||
}
|
||||
}
|
||||
|
||||
msg.Write(isNewOrder);
|
||||
msg.WriteBoolean(isNewOrder);
|
||||
}
|
||||
|
||||
private void WriteOrder(IWriteMessage msg)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
abstract class AccountId
|
||||
{
|
||||
public abstract string StringRepresentation { get; }
|
||||
|
||||
public static Option<AccountId> Parse(string str)
|
||||
=> ReflectionUtils.ParseDerived<AccountId, string>(str);
|
||||
|
||||
public abstract override bool Equals(object? obj);
|
||||
|
||||
public abstract override int GetHashCode();
|
||||
|
||||
public override string ToString() => StringRepresentation;
|
||||
|
||||
public static bool operator ==(AccountId a, AccountId b)
|
||||
=> a.Equals(b);
|
||||
|
||||
public static bool operator !=(AccountId a, AccountId b)
|
||||
=> !(a == b);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
sealed class SteamId : AccountId
|
||||
{
|
||||
public readonly UInt64 Value;
|
||||
|
||||
public override string StringRepresentation { get; }
|
||||
|
||||
/// Based on information found here: https://developer.valvesoftware.com/wiki/SteamID
|
||||
/// ------------------------------------------------------------------------------------
|
||||
/// A SteamID is a 64-bit value (16 hexadecimal digits) that's broken up as follows:
|
||||
///
|
||||
/// | a | b | c | d |
|
||||
/// Most significant - | 01 | 1 | 00001 | 0546779D | - Least significant
|
||||
///
|
||||
/// a) 8 bits representing the universe the account belongs to.
|
||||
/// b) 4 bits representing the type of account. Typically 1.
|
||||
/// c) 20 bits representing the instance of the account. Typically 1.
|
||||
/// d) 32 bits representing the account number.
|
||||
///
|
||||
/// The account number is additionally broken up as follows:
|
||||
///
|
||||
/// | e | f |
|
||||
/// Most significant - | 0000010101000110011101111001110 | 1 | - Least significant
|
||||
///
|
||||
/// e) These are the 31 most significant bits of the account number.
|
||||
/// f) This is the least significant bit of the account number, discriminated under the name Y for some reason.
|
||||
///
|
||||
/// Barotrauma supports two textual representations of SteamIDs:
|
||||
/// 1. STEAM40: Given this name as it represents 40 of the 64 bits in the ID. The account type and instance both
|
||||
/// have an implied value of 1. The format is "STEAM_{universe}:{Y}:{restOfAccountNumber}".
|
||||
/// 2. STEAM64: If STEAM40 does not suffice to represent an ID (i.e. the account type or instance were different
|
||||
/// from 1), we use "STEAM64_{fullId}" where fullId is the 64-bit decimal representation of the full
|
||||
/// ID.
|
||||
|
||||
private const string steam64Prefix = "STEAM64_";
|
||||
private const string steam40Prefix = "STEAM_";
|
||||
|
||||
private const UInt64 usualAccountInstance = 1;
|
||||
private const UInt64 usualAccountType = 1;
|
||||
|
||||
static UInt64 ExtractBits(UInt64 id, int offset, int numberOfBits)
|
||||
=> (id >> offset) & ((1ul << numberOfBits) - 1ul);
|
||||
|
||||
static UInt64 ExtractY(UInt64 id)
|
||||
=> ExtractBits(id, offset: 0, numberOfBits: 1);
|
||||
static UInt64 ExtractAccountNumberRemainder(UInt64 id)
|
||||
=> ExtractBits(id, offset: 1, numberOfBits: 31);
|
||||
static UInt64 ExtractAccountInstance(UInt64 id)
|
||||
=> ExtractBits(id, offset: 32, numberOfBits: 20);
|
||||
static UInt64 ExtractAccountType(UInt64 id)
|
||||
=> ExtractBits(id, offset: 52, numberOfBits: 4);
|
||||
static UInt64 ExtractUniverse(UInt64 id)
|
||||
=> ExtractBits(id, offset: 56, numberOfBits: 8);
|
||||
|
||||
public SteamId(UInt64 value)
|
||||
{
|
||||
Value = value;
|
||||
|
||||
if (ExtractAccountInstance(Value) == usualAccountInstance
|
||||
&& ExtractAccountType(Value) == usualAccountType)
|
||||
{
|
||||
UInt64 y = ExtractY(Value);
|
||||
UInt64 accountNumberRemainder = ExtractAccountNumberRemainder(Value);
|
||||
UInt64 universe = ExtractUniverse(Value);
|
||||
StringRepresentation = $"{steam40Prefix}{universe}:{y}:{accountNumberRemainder}";
|
||||
}
|
||||
else
|
||||
{
|
||||
StringRepresentation = $"{steam64Prefix}{Value}";
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => StringRepresentation;
|
||||
|
||||
public new static Option<SteamId> Parse(string str)
|
||||
{
|
||||
if (str.IsNullOrWhiteSpace()) { return Option<SteamId>.None(); }
|
||||
|
||||
if (str.StartsWith(steam64Prefix, StringComparison.InvariantCultureIgnoreCase)) { str = str[steam64Prefix.Length..]; }
|
||||
if (UInt64.TryParse(str, out UInt64 retVal) && ExtractAccountInstance(retVal) > 0)
|
||||
{
|
||||
return Option<SteamId>.Some(new SteamId(retVal));
|
||||
}
|
||||
|
||||
if (!str.StartsWith(steam40Prefix, StringComparison.InvariantCultureIgnoreCase)) { return Option<SteamId>.None(); }
|
||||
string[] split = str[steam40Prefix.Length..].Split(':');
|
||||
if (split.Length != 3) { return Option<SteamId>.None(); }
|
||||
|
||||
if (!UInt64.TryParse(split[0], out UInt64 universe)) { return Option<SteamId>.None(); }
|
||||
if (!UInt64.TryParse(split[1], out UInt64 y)) { return Option<SteamId>.None(); }
|
||||
if (!UInt64.TryParse(split[2], out UInt64 accountNumber)) { return Option<SteamId>.None(); }
|
||||
|
||||
return Option<SteamId>.Some(
|
||||
new SteamId((universe << 56)
|
||||
| usualAccountType << 52
|
||||
| usualAccountInstance << 32
|
||||
| (accountNumber << 1)
|
||||
| y));
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj switch
|
||||
{
|
||||
SteamId otherId => this == otherId,
|
||||
_ => false
|
||||
};
|
||||
|
||||
public override int GetHashCode()
|
||||
=> Value.GetHashCode();
|
||||
|
||||
public static bool operator ==(SteamId a, SteamId b)
|
||||
=> a.Value == b.Value;
|
||||
|
||||
public static bool operator !=(SteamId a, SteamId b)
|
||||
=> !(a == b);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
#nullable enable
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
[NetworkSerialize]
|
||||
readonly struct AccountInfo : INetSerializableStruct
|
||||
{
|
||||
public static readonly AccountInfo None = new AccountInfo(Option<AccountId>.None());
|
||||
|
||||
/// <summary>
|
||||
/// The primary ID for a given user
|
||||
/// </summary>
|
||||
public readonly Option<AccountId> AccountId;
|
||||
|
||||
/// <summary>
|
||||
/// Other user IDs that this user might be closely tied to,
|
||||
/// such as the owner of the current copy of Barotrauma
|
||||
/// </summary>
|
||||
#warning TODO: make ImmutableArray once feature/inetserializablestruct-improvements gets merged to dev
|
||||
public readonly AccountId[] OtherMatchingIds;
|
||||
|
||||
public AccountInfo(AccountId accountId, params AccountId[] otherIds) : this(Option<AccountId>.Some(accountId), otherIds) { }
|
||||
|
||||
public AccountInfo(Option<AccountId> accountId, params AccountId[] otherIds)
|
||||
{
|
||||
AccountId = accountId;
|
||||
OtherMatchingIds = otherIds.Where(id => !accountId.ValueEquals(id)).ToArray();
|
||||
}
|
||||
|
||||
public bool Matches(AccountId accountId)
|
||||
=> AccountId.ValueEquals(accountId) || OtherMatchingIds.Contains(accountId);
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj switch
|
||||
{
|
||||
AccountInfo otherInfo => AccountId == otherInfo.AccountId && OtherMatchingIds.All(otherInfo.OtherMatchingIds.Contains),
|
||||
_ => false
|
||||
};
|
||||
|
||||
public override int GetHashCode()
|
||||
=> AccountId.GetHashCode();
|
||||
|
||||
public static bool operator ==(AccountInfo a, AccountInfo b)
|
||||
=> a.Equals(b);
|
||||
|
||||
public static bool operator !=(AccountInfo a, AccountInfo b) => !(a == b);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
abstract class Address
|
||||
{
|
||||
public abstract string StringRepresentation { get; }
|
||||
|
||||
public static Option<Address> Parse(string str)
|
||||
=> ReflectionUtils.ParseDerived<Address, string>(str);
|
||||
|
||||
public abstract bool IsLocalHost { get; }
|
||||
|
||||
public abstract override bool Equals(object? obj);
|
||||
|
||||
public abstract override int GetHashCode();
|
||||
|
||||
public override string ToString() => StringRepresentation;
|
||||
|
||||
public static bool operator ==(Address a, Address b)
|
||||
=> a.Equals(b);
|
||||
|
||||
public static bool operator !=(Address a, Address b)
|
||||
=> !(a == b);
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
sealed class LidgrenAddress : Address
|
||||
{
|
||||
public readonly IPAddress NetAddress;
|
||||
|
||||
public override string StringRepresentation
|
||||
=> NetAddress.ToString();
|
||||
|
||||
public override bool IsLocalHost => IPAddress.IsLoopback(NetAddress);
|
||||
|
||||
public LidgrenAddress(IPAddress netAddress)
|
||||
{
|
||||
if (IPAddress.IsLoopback(netAddress))
|
||||
{
|
||||
NetAddress = IPAddress.Loopback;
|
||||
}
|
||||
else
|
||||
{
|
||||
NetAddress = netAddress;
|
||||
}
|
||||
}
|
||||
|
||||
public new static Option<LidgrenAddress> Parse(string endpointStr)
|
||||
{
|
||||
if (endpointStr.Equals("localhost", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Option<LidgrenAddress>.Some(new LidgrenAddress(IPAddress.Loopback));
|
||||
}
|
||||
else if (IPAddress.TryParse(endpointStr, out IPAddress? netEndpoint))
|
||||
{
|
||||
return Option<LidgrenAddress>.Some(new LidgrenAddress(netEndpoint!));
|
||||
}
|
||||
return Option<LidgrenAddress>.None();
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj switch
|
||||
{
|
||||
LidgrenAddress otherAddress => this == otherAddress,
|
||||
_ => false
|
||||
};
|
||||
|
||||
public override int GetHashCode()
|
||||
=> NetAddress.GetHashCode();
|
||||
|
||||
public static bool operator ==(LidgrenAddress a, LidgrenAddress b)
|
||||
{
|
||||
var addressA = a.NetAddress.MapToIPv6();
|
||||
var addressB = b.NetAddress.MapToIPv6();
|
||||
|
||||
if (IPAddress.IsLoopback(addressA) && IPAddress.IsLoopback(addressB)) { return true; }
|
||||
return addressA.Equals(addressB);
|
||||
}
|
||||
|
||||
public static bool operator !=(LidgrenAddress a, LidgrenAddress b)
|
||||
=> !(a == b);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
sealed class PipeAddress : Address
|
||||
{
|
||||
public override string StringRepresentation => "PIPE";
|
||||
|
||||
public override bool IsLocalHost => true;
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is PipeAddress;
|
||||
|
||||
public override int GetHashCode() => 1;
|
||||
|
||||
public static bool operator ==(PipeAddress a, PipeAddress b)
|
||||
=> true;
|
||||
|
||||
public static bool operator !=(PipeAddress a, PipeAddress b)
|
||||
=> !(a == b);
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
sealed class SteamP2PAddress : Address
|
||||
{
|
||||
public readonly SteamId SteamId;
|
||||
|
||||
public override string StringRepresentation => SteamId.StringRepresentation;
|
||||
|
||||
public override bool IsLocalHost => false;
|
||||
|
||||
public SteamP2PAddress(SteamId steamId)
|
||||
{
|
||||
SteamId = steamId;
|
||||
}
|
||||
|
||||
public new static Option<SteamP2PAddress> Parse(string endpointStr)
|
||||
=> SteamId.Parse(endpointStr).Select(steamId => new SteamP2PAddress(steamId));
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj switch
|
||||
{
|
||||
SteamP2PAddress otherAddress => this == otherAddress,
|
||||
_ => false
|
||||
};
|
||||
|
||||
public override int GetHashCode()
|
||||
=> SteamId.GetHashCode();
|
||||
|
||||
public static bool operator ==(SteamP2PAddress a, SteamP2PAddress b)
|
||||
=> a.SteamId == b.SteamId;
|
||||
|
||||
public static bool operator !=(SteamP2PAddress a, SteamP2PAddress b)
|
||||
=> !(a == b);
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
sealed class UnknownAddress : Address
|
||||
{
|
||||
public override string StringRepresentation => "Hidden";
|
||||
|
||||
public override bool IsLocalHost => false;
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> ReferenceEquals(obj, this);
|
||||
|
||||
public override int GetHashCode() => 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
abstract class Endpoint
|
||||
{
|
||||
public abstract string StringRepresentation { get; }
|
||||
|
||||
public abstract LocalizedString ServerTypeString { get; }
|
||||
|
||||
public readonly Address Address;
|
||||
|
||||
public Endpoint(Address address)
|
||||
{
|
||||
Address = address;
|
||||
}
|
||||
|
||||
public abstract override bool Equals(object? obj);
|
||||
|
||||
public abstract override int GetHashCode();
|
||||
|
||||
public override string ToString() => StringRepresentation;
|
||||
|
||||
public static Option<Endpoint> Parse(string str)
|
||||
=> ReflectionUtils.ParseDerived<Endpoint, string>(str);
|
||||
|
||||
public static bool operator ==(Endpoint? a, Endpoint? b)
|
||||
{
|
||||
if (a is null)
|
||||
{
|
||||
return b is null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator !=(Endpoint? a, Endpoint? b)
|
||||
=> !(a == b);
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
#nullable enable
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
sealed class LidgrenEndpoint : Endpoint
|
||||
{
|
||||
public readonly IPEndPoint NetEndpoint;
|
||||
|
||||
public int Port => NetEndpoint.Port;
|
||||
|
||||
public override string StringRepresentation
|
||||
=> NetEndpoint.ToString();
|
||||
|
||||
public override LocalizedString ServerTypeString { get; } = TextManager.Get("DedicatedServer");
|
||||
|
||||
public LidgrenEndpoint(IPAddress address, int port) : this(new IPEndPoint(address, port)) { }
|
||||
|
||||
public LidgrenEndpoint(IPEndPoint netEndpoint) : base(new LidgrenAddress(netEndpoint.Address))
|
||||
{
|
||||
NetEndpoint = netEndpoint;
|
||||
}
|
||||
|
||||
public new static Option<LidgrenEndpoint> Parse(string endpointStr)
|
||||
{
|
||||
string hostName = endpointStr;
|
||||
int port = NetConfig.DefaultPort;
|
||||
if (endpointStr.Count(c => c == ':') == 1)
|
||||
{
|
||||
string[] split = endpointStr.Split(':');
|
||||
hostName = split[0];
|
||||
port = int.TryParse(split[1], out var tmpPort) ? tmpPort : port;
|
||||
}
|
||||
|
||||
if (LidgrenAddress.Parse(hostName).TryUnwrap(out var adr))
|
||||
{
|
||||
return Option<LidgrenEndpoint>.Some(new LidgrenEndpoint(adr.NetAddress, port));
|
||||
}
|
||||
|
||||
return IPEndPoint.TryParse(endpointStr, out IPEndPoint? netEndpoint)
|
||||
? Option<LidgrenEndpoint>.Some(new LidgrenEndpoint(netEndpoint))
|
||||
: Option<LidgrenEndpoint>.None();
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj switch
|
||||
{
|
||||
LidgrenEndpoint otherEndpoint => this == otherEndpoint,
|
||||
_ => false
|
||||
};
|
||||
|
||||
public override int GetHashCode()
|
||||
=> NetEndpoint.GetHashCode();
|
||||
|
||||
public static bool operator ==(LidgrenEndpoint a, LidgrenEndpoint b)
|
||||
=> a.Address.Equals(b.Address) && a.Port == b.Port;
|
||||
|
||||
public static bool operator !=(LidgrenEndpoint a, LidgrenEndpoint b)
|
||||
=> !(a == b);
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
sealed class SteamP2PEndpoint : Endpoint
|
||||
{
|
||||
public readonly SteamId SteamId;
|
||||
|
||||
public override string StringRepresentation => SteamId.StringRepresentation;
|
||||
|
||||
public override LocalizedString ServerTypeString { get; } = TextManager.Get("SteamP2PServer");
|
||||
|
||||
public SteamP2PEndpoint(SteamId steamId) : base(new SteamP2PAddress(steamId))
|
||||
{
|
||||
SteamId = steamId;
|
||||
}
|
||||
|
||||
public new static Option<SteamP2PEndpoint> Parse(string endpointStr)
|
||||
=> SteamId.Parse(endpointStr).Select(steamId => new SteamP2PEndpoint(steamId));
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj switch
|
||||
{
|
||||
SteamP2PEndpoint otherEndpoint => this == otherEndpoint,
|
||||
_ => false
|
||||
};
|
||||
|
||||
public override int GetHashCode()
|
||||
=> SteamId.GetHashCode();
|
||||
|
||||
public static bool operator ==(SteamP2PEndpoint a, SteamP2PEndpoint b)
|
||||
=> a.SteamId == b.SteamId;
|
||||
|
||||
public static bool operator !=(SteamP2PEndpoint a, SteamP2PEndpoint b)
|
||||
=> !(a == b);
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -4,11 +4,12 @@ using System.Text;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
public interface IReadMessage
|
||||
interface IReadMessage
|
||||
{
|
||||
bool ReadBoolean();
|
||||
void ReadPadBits();
|
||||
byte ReadByte();
|
||||
byte PeekByte();
|
||||
UInt16 ReadUInt16();
|
||||
Int16 ReadInt16();
|
||||
UInt32 ReadUInt32();
|
||||
|
||||
+15
-15
@@ -2,29 +2,29 @@
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
public interface IWriteMessage
|
||||
interface IWriteMessage
|
||||
{
|
||||
void Write(bool val);
|
||||
void WriteBoolean(bool val);
|
||||
void WritePadBits();
|
||||
void Write(byte val);
|
||||
void Write(Int16 val);
|
||||
void Write(UInt16 val);
|
||||
void Write(Int32 val);
|
||||
void Write(UInt32 val);
|
||||
void Write(Int64 val);
|
||||
void Write(UInt64 val);
|
||||
void Write(Single val);
|
||||
void Write(Double val);
|
||||
void WriteByte(byte val);
|
||||
void WriteInt16(Int16 val);
|
||||
void WriteUInt16(UInt16 val);
|
||||
void WriteInt32(Int32 val);
|
||||
void WriteUInt32(UInt32 val);
|
||||
void WriteInt64(Int64 val);
|
||||
void WriteUInt64(UInt64 val);
|
||||
void WriteSingle(Single val);
|
||||
void WriteDouble(Double val);
|
||||
void WriteColorR8G8B8(Microsoft.Xna.Framework.Color val);
|
||||
void WriteColorR8G8B8A8(Microsoft.Xna.Framework.Color val);
|
||||
void WriteVariableUInt32(UInt32 val);
|
||||
void Write(string val);
|
||||
void Write(Identifier val);
|
||||
void WriteString(string val);
|
||||
void WriteIdentifier(Identifier val);
|
||||
void WriteRangedInteger(int val, int min, int max);
|
||||
void WriteRangedSingle(Single val, Single min, Single max, int bitCount);
|
||||
void Write(byte[] val, int startIndex, int length);
|
||||
void WriteBytes(byte[] val, int startIndex, int length);
|
||||
|
||||
void PrepareForSending(ref byte[] outBuf, bool compressPastThreshold, out bool isCompressed, out int outLength);
|
||||
byte[] PrepareForSending(bool compressPastThreshold, out bool isCompressed, out int outLength);
|
||||
|
||||
int BitPosition { get; set; }
|
||||
int BytePosition { get; }
|
||||
|
||||
+176
-234
@@ -1,7 +1,6 @@
|
||||
using Lidgren.Network;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.IO;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
@@ -11,7 +10,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
public static class MsgConstants
|
||||
{
|
||||
public const int MTU = 1200;
|
||||
public const int MTU = 1200; //TODO: determine dynamically
|
||||
public const int CompressionThreshold = 1000;
|
||||
public const int InitialBufferSize = 256;
|
||||
public const int BufferOverAllocateAmount = 4;
|
||||
@@ -58,7 +57,7 @@ namespace Barotrauma.Networking
|
||||
bool testVal = MsgReader.ReadBoolean(buf, ref resetPos);
|
||||
if (testVal != val || resetPos != bitPos)
|
||||
{
|
||||
DebugConsole.ThrowError("Boolean written incorrectly! " + testVal + ", " + val + "; " + resetPos + ", " + bitPos);
|
||||
DebugConsole.ThrowError($"Boolean written incorrectly! {testVal}, {val}; {resetPos}, {bitPos}");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -125,7 +124,7 @@ namespace Barotrauma.Networking
|
||||
SingleUIntUnion su;
|
||||
su.UIntValue = 0; // must initialize every member of the union to avoid warning
|
||||
su.SingleValue = val;
|
||||
|
||||
|
||||
EnsureBufferSize(ref buf, bitPos + 32);
|
||||
|
||||
NetBitWriter.WriteUInt32(su.UIntValue, 32, buf, bitPos);
|
||||
@@ -140,50 +139,48 @@ namespace Barotrauma.Networking
|
||||
WriteBytes(ref buf, ref bitPos, bytes, 0, 8);
|
||||
}
|
||||
|
||||
internal static void WriteColorR8G8B8(ref byte[] buf, ref int bitPos, Microsoft.Xna.Framework.Color val)
|
||||
internal static void WriteColorR8G8B8(ref byte[] buf, ref int bitPos, Color val)
|
||||
{
|
||||
EnsureBufferSize(ref buf, bitPos + 24);
|
||||
|
||||
|
||||
Write(ref buf, ref bitPos, val.R);
|
||||
Write(ref buf, ref bitPos, val.G);
|
||||
Write(ref buf, ref bitPos, val.B);
|
||||
}
|
||||
|
||||
internal static void WriteColorR8G8B8A8(ref byte[] buf, ref int bitPos, Microsoft.Xna.Framework.Color val)
|
||||
|
||||
internal static void WriteColorR8G8B8A8(ref byte[] buf, ref int bitPos, Color val)
|
||||
{
|
||||
EnsureBufferSize(ref buf, bitPos + 32);
|
||||
|
||||
|
||||
Write(ref buf, ref bitPos, val.R);
|
||||
Write(ref buf, ref bitPos, val.G);
|
||||
Write(ref buf, ref bitPos, val.B);
|
||||
Write(ref buf, ref bitPos, val.A);
|
||||
}
|
||||
|
||||
|
||||
internal static void Write(ref byte[] buf, ref int bitPos, string val)
|
||||
{
|
||||
if (string.IsNullOrEmpty(val))
|
||||
{
|
||||
WriteVariableUInt32(ref buf, ref bitPos, (uint)0);
|
||||
WriteVariableUInt32(ref buf, ref bitPos, 0u);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(val);
|
||||
WriteVariableUInt32(ref buf, ref bitPos, (uint)bytes.Length);
|
||||
WriteBytes(ref buf, ref bitPos, bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
internal static int WriteVariableUInt32(ref byte[] buf, ref int bitPos, uint value)
|
||||
internal static void WriteVariableUInt32(ref byte[] buf, ref int bitPos, uint value)
|
||||
{
|
||||
int retval = 1;
|
||||
uint remainingValue = (uint)value;
|
||||
uint remainingValue = value;
|
||||
while (remainingValue >= 0x80)
|
||||
{
|
||||
Write(ref buf, ref bitPos, (byte)(remainingValue | 0x80));
|
||||
remainingValue = remainingValue >> 7;
|
||||
retval++;
|
||||
remainingValue >>= 7;
|
||||
}
|
||||
|
||||
Write(ref buf, ref bitPos, (byte)remainingValue);
|
||||
return retval;
|
||||
}
|
||||
|
||||
internal static void WriteRangedInteger(ref byte[] buf, ref int bitPos, int val, int min, int max)
|
||||
@@ -206,7 +203,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
EnsureBufferSize(ref buf, bitPos + numberOfBits);
|
||||
|
||||
NetBitWriter.WriteUInt32((UInt32)((float)maxVal * unit), numberOfBits, buf, bitPos);
|
||||
NetBitWriter.WriteUInt32((UInt32)(maxVal * unit), numberOfBits, buf, bitPos);
|
||||
bitPos += numberOfBits;
|
||||
}
|
||||
|
||||
@@ -225,9 +222,10 @@ namespace Barotrauma.Networking
|
||||
buf = new byte[byteLen + MsgConstants.BufferOverAllocateAmount];
|
||||
return;
|
||||
}
|
||||
|
||||
if (buf.Length < byteLen)
|
||||
{
|
||||
Array.Resize<byte>(ref buf, byteLen + MsgConstants.BufferOverAllocateAmount);
|
||||
Array.Resize(ref buf, byteLen + MsgConstants.BufferOverAllocateAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -241,7 +239,7 @@ namespace Barotrauma.Networking
|
||||
return retval > 0;
|
||||
}
|
||||
|
||||
internal static void ReadPadBits(byte[] buf, ref int bitPos)
|
||||
internal static void ReadPadBits(ref int bitPos)
|
||||
{
|
||||
int bitOffset = bitPos % 8;
|
||||
bitPos += (8 - bitOffset) % 8;
|
||||
@@ -254,6 +252,12 @@ namespace Barotrauma.Networking
|
||||
return retval;
|
||||
}
|
||||
|
||||
internal static byte PeekByte(byte[] buf, ref int bitPos)
|
||||
{
|
||||
byte retval = NetBitWriter.ReadByte(buf, 8, bitPos);
|
||||
return retval;
|
||||
}
|
||||
|
||||
internal static UInt16 ReadUInt16(byte[] buf, ref int bitPos)
|
||||
{
|
||||
uint retval = NetBitWriter.ReadUInt16(buf, 16, bitPos);
|
||||
@@ -320,15 +324,15 @@ namespace Barotrauma.Networking
|
||||
return BitConverter.ToDouble(bytes, 0);
|
||||
}
|
||||
|
||||
internal static Microsoft.Xna.Framework.Color ReadColorR8G8B8(byte[] buf, ref int bitPos)
|
||||
internal static Color ReadColorR8G8B8(byte[] buf, ref int bitPos)
|
||||
{
|
||||
byte r = ReadByte(buf, ref bitPos);
|
||||
byte g = ReadByte(buf, ref bitPos);
|
||||
byte b = ReadByte(buf, ref bitPos);
|
||||
return new Color(r, g, b, (byte)255);
|
||||
}
|
||||
|
||||
internal static Microsoft.Xna.Framework.Color ReadColorR8G8B8A8(byte[] buf, ref int bitPos)
|
||||
|
||||
internal static Color ReadColorR8G8B8A8(byte[] buf, ref int bitPos)
|
||||
{
|
||||
byte r = ReadByte(buf, ref bitPos);
|
||||
byte g = ReadByte(buf, ref bitPos);
|
||||
@@ -348,8 +352,7 @@ namespace Barotrauma.Networking
|
||||
byte chunk = ReadByte(buf, ref bitPos);
|
||||
result |= (chunk & 0x7f) << shift;
|
||||
shift += 7;
|
||||
if ((chunk & 0x80) == 0)
|
||||
return (uint)result;
|
||||
if ((chunk & 0x80) == 0) { return (uint)result; }
|
||||
}
|
||||
|
||||
// ouch; failed to find enough bytes; malformed variable length number?
|
||||
@@ -372,23 +375,23 @@ namespace Barotrauma.Networking
|
||||
if ((bitPos & 7) == 0)
|
||||
{
|
||||
// read directly
|
||||
string retval = System.Text.Encoding.UTF8.GetString(buf, bitPos >> 3, byteLen);
|
||||
string retval = Encoding.UTF8.GetString(buf, bitPos >> 3, byteLen);
|
||||
bitPos += (8 * byteLen);
|
||||
return retval;
|
||||
}
|
||||
|
||||
byte[] bytes = ReadBytes(buf, ref bitPos, byteLen);
|
||||
return System.Text.Encoding.UTF8.GetString(bytes, 0, bytes.Length);
|
||||
return Encoding.UTF8.GetString(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
internal static int ReadRangedInteger(byte[] buf, ref int bitPos, int min, int max)
|
||||
{
|
||||
uint range = (uint)(max - min);
|
||||
int numBits = NetUtility.BitsToHoldUInt(range);
|
||||
uint range = (uint)(max - min);
|
||||
int numBits = NetUtility.BitsToHoldUInt(range);
|
||||
|
||||
uint rvalue = NetBitWriter.ReadUInt32(buf, numBits, bitPos);
|
||||
uint rvalue = NetBitWriter.ReadUInt32(buf, numBits, bitPos);
|
||||
bitPos += numBits;
|
||||
|
||||
|
||||
return (int)(min + rvalue);
|
||||
}
|
||||
|
||||
@@ -397,51 +400,33 @@ namespace Barotrauma.Networking
|
||||
int maxInt = (1 << bitCount) - 1;
|
||||
int intVal = ReadRangedInteger(buf, ref bitPos, 0, maxInt);
|
||||
Single range = max - min;
|
||||
return min + (range * ((Single)intVal) / ((Single)maxInt));
|
||||
return min + range * intVal / maxInt;
|
||||
}
|
||||
|
||||
internal static byte[] ReadBytes(byte[] buf, ref int bitPos, int numberOfBytes)
|
||||
{
|
||||
byte[] retval = new byte[numberOfBytes];
|
||||
NetBitWriter.ReadBytes(buf, numberOfBytes, bitPos, retval, 0);
|
||||
bitPos += (8 * numberOfBytes);
|
||||
bitPos += 8 * numberOfBytes;
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
|
||||
public class WriteOnlyMessage : IWriteMessage
|
||||
internal sealed class WriteOnlyMessage : IWriteMessage
|
||||
{
|
||||
private byte[] buf = new byte[MsgConstants.InitialBufferSize];
|
||||
private int seekPos = 0;
|
||||
private int lengthBits = 0;
|
||||
private int seekPos;
|
||||
private int lengthBits;
|
||||
|
||||
public int BitPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return seekPos;
|
||||
}
|
||||
set
|
||||
{
|
||||
seekPos = value;
|
||||
}
|
||||
get => seekPos;
|
||||
set => seekPos = value;
|
||||
}
|
||||
|
||||
public int BytePosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return seekPos / 8;
|
||||
}
|
||||
}
|
||||
public int BytePosition => seekPos / 8;
|
||||
|
||||
public byte[] Buffer
|
||||
{
|
||||
get
|
||||
{
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
public byte[] Buffer => buf;
|
||||
|
||||
public int LengthBits
|
||||
{
|
||||
@@ -458,15 +443,9 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
public int LengthBytes
|
||||
{
|
||||
get
|
||||
{
|
||||
return (LengthBits + 7) / 8;
|
||||
}
|
||||
}
|
||||
public int LengthBytes => (LengthBits + 7) / 8;
|
||||
|
||||
public void Write(bool val)
|
||||
public void WriteBoolean(bool val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
@@ -476,47 +455,47 @@ namespace Barotrauma.Networking
|
||||
MsgWriter.WritePadBits(ref buf, ref seekPos);
|
||||
}
|
||||
|
||||
public void Write(byte val)
|
||||
public void WriteByte(byte val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(UInt16 val)
|
||||
public void WriteUInt16(UInt16 val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(Int16 val)
|
||||
public void WriteInt16(Int16 val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(UInt32 val)
|
||||
public void WriteUInt32(UInt32 val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(Int32 val)
|
||||
public void WriteInt32(Int32 val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(UInt64 val)
|
||||
public void WriteUInt64(UInt64 val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(Int64 val)
|
||||
public void WriteInt64(Int64 val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(Single val)
|
||||
public void WriteSingle(Single val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(Double val)
|
||||
public void WriteDouble(Double val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
@@ -525,7 +504,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
MsgWriter.WriteColorR8G8B8(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
|
||||
public void WriteColorR8G8B8A8(Color val)
|
||||
{
|
||||
MsgWriter.WriteColorR8G8B8A8(ref buf, ref seekPos, val);
|
||||
@@ -536,14 +515,14 @@ namespace Barotrauma.Networking
|
||||
MsgWriter.WriteVariableUInt32(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(String val)
|
||||
public void WriteString(String val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(Identifier val)
|
||||
public void WriteIdentifier(Identifier val)
|
||||
{
|
||||
Write(val.Value);
|
||||
WriteString(val.Value);
|
||||
}
|
||||
|
||||
public void WriteRangedInteger(int val, int min, int max)
|
||||
@@ -556,85 +535,67 @@ namespace Barotrauma.Networking
|
||||
MsgWriter.WriteRangedSingle(ref buf, ref seekPos, val, min, max, bitCount);
|
||||
}
|
||||
|
||||
public void Write(byte[] val, int startPos, int length)
|
||||
public void WriteBytes(byte[] val, int startPos, int length)
|
||||
{
|
||||
MsgWriter.WriteBytes(ref buf, ref seekPos, val, startPos, length);
|
||||
}
|
||||
|
||||
public void PrepareForSending(ref byte[] outBuf, bool compressPastThreshold, out bool isCompressed, out int length)
|
||||
|
||||
public byte[] PrepareForSending(bool compressPastThreshold, out bool isCompressed, out int length)
|
||||
{
|
||||
byte[] outBuf;
|
||||
if (LengthBytes <= MsgConstants.CompressionThreshold || !compressPastThreshold)
|
||||
{
|
||||
isCompressed = false;
|
||||
if (LengthBytes > outBuf.Length) { Array.Resize(ref outBuf, LengthBytes); }
|
||||
outBuf = new byte[LengthBytes];
|
||||
Array.Copy(buf, outBuf, LengthBytes);
|
||||
length = LengthBytes;
|
||||
}
|
||||
else
|
||||
{
|
||||
using (System.IO.MemoryStream output = new System.IO.MemoryStream())
|
||||
using MemoryStream output = new MemoryStream();
|
||||
|
||||
using (DeflateStream dstream = new DeflateStream(output, CompressionLevel.Fastest))
|
||||
{
|
||||
using (DeflateStream dstream = new DeflateStream(output, CompressionLevel.Fastest))
|
||||
{
|
||||
dstream.Write(buf, 0, LengthBytes);
|
||||
}
|
||||
|
||||
byte[] compressedBuf = output.ToArray();
|
||||
//don't send the data as compressed if the data takes up more space after compression
|
||||
//(which may happen when sending a sub/save file that's already been compressed with a better compression ratio)
|
||||
if (compressedBuf.Length >= outBuf.Length)
|
||||
{
|
||||
isCompressed = false;
|
||||
if (LengthBytes > outBuf.Length) { Array.Resize(ref outBuf, LengthBytes); }
|
||||
Array.Copy(buf, outBuf, LengthBytes);
|
||||
length = LengthBytes;
|
||||
}
|
||||
else
|
||||
{
|
||||
isCompressed = true;
|
||||
if (compressedBuf.Length > outBuf.Length) { Array.Resize(ref outBuf, compressedBuf.Length); }
|
||||
Array.Copy(compressedBuf, outBuf, compressedBuf.Length);
|
||||
length = compressedBuf.Length;
|
||||
DebugConsole.Log("Compressed message: " + LengthBytes + " to " + length);
|
||||
}
|
||||
dstream.Write(buf, 0, LengthBytes);
|
||||
}
|
||||
|
||||
byte[] compressedBuf = output.ToArray();
|
||||
//don't send the data as compressed if the data takes up more space after compression
|
||||
//(which may happen when sending a sub/save file that's already been compressed with a better compression ratio)
|
||||
if (compressedBuf.Length >= LengthBytes)
|
||||
{
|
||||
isCompressed = false;
|
||||
outBuf = new byte[LengthBytes];
|
||||
Array.Copy(buf, outBuf, LengthBytes);
|
||||
length = LengthBytes;
|
||||
}
|
||||
else
|
||||
{
|
||||
isCompressed = true;
|
||||
outBuf = compressedBuf;
|
||||
length = outBuf.Length;
|
||||
DebugConsole.Log($"Compressed message: {LengthBytes} to {length}");
|
||||
}
|
||||
}
|
||||
|
||||
return outBuf;
|
||||
}
|
||||
}
|
||||
|
||||
public class ReadOnlyMessage : IReadMessage
|
||||
internal sealed class ReadOnlyMessage : IReadMessage
|
||||
{
|
||||
private byte[] buf;
|
||||
private int seekPos = 0;
|
||||
private int lengthBits = 0;
|
||||
private int seekPos;
|
||||
private int lengthBits;
|
||||
|
||||
public int BitPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return seekPos;
|
||||
}
|
||||
set
|
||||
{
|
||||
seekPos = value;
|
||||
}
|
||||
get => seekPos;
|
||||
set => seekPos = value;
|
||||
}
|
||||
|
||||
public int BytePosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return seekPos / 8;
|
||||
}
|
||||
}
|
||||
public int BytePosition => seekPos / 8;
|
||||
|
||||
public byte[] Buffer
|
||||
{
|
||||
get
|
||||
{
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
public byte[] Buffer { get; }
|
||||
|
||||
public int LengthBits
|
||||
{
|
||||
@@ -650,124 +611,126 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
public int LengthBytes
|
||||
{
|
||||
get
|
||||
{
|
||||
return (LengthBits + 7) / 8;
|
||||
}
|
||||
}
|
||||
public int LengthBytes => (LengthBits + 7) / 8;
|
||||
|
||||
public NetworkConnection Sender { get; private set; }
|
||||
|
||||
public ReadOnlyMessage(byte[] inBuf, bool isCompressed, int startPos, int inLength, NetworkConnection sender)
|
||||
public NetworkConnection Sender { get; }
|
||||
|
||||
public ReadOnlyMessage(byte[] inBuf, bool isCompressed, int startPos, int byteLength, NetworkConnection sender)
|
||||
{
|
||||
Sender = sender;
|
||||
if (isCompressed)
|
||||
{
|
||||
byte[] decompressedData;
|
||||
using (System.IO.MemoryStream input = new System.IO.MemoryStream(inBuf, startPos, inLength))
|
||||
using (MemoryStream input = new MemoryStream(inBuf, startPos, byteLength))
|
||||
{
|
||||
using (System.IO.MemoryStream output = new System.IO.MemoryStream())
|
||||
using (MemoryStream output = new MemoryStream())
|
||||
{
|
||||
using (DeflateStream dstream = new DeflateStream(input, CompressionMode.Decompress))
|
||||
{
|
||||
dstream.CopyTo(output);
|
||||
}
|
||||
|
||||
decompressedData = output.ToArray();
|
||||
}
|
||||
}
|
||||
buf = new byte[decompressedData.Length];
|
||||
|
||||
Buffer = new byte[decompressedData.Length];
|
||||
try
|
||||
{
|
||||
Array.Copy(decompressedData, 0, buf, 0, decompressedData.Length);
|
||||
Array.Copy(decompressedData, 0, Buffer, 0, decompressedData.Length);
|
||||
}
|
||||
catch (ArgumentException e)
|
||||
{
|
||||
throw new ArgumentException($"Failed to copy the incoming compressed buffer. Source buffer length: {decompressedData.Length}, start position: {0}, length: {decompressedData.Length}, destination buffer length: {buf.Length}.", e);
|
||||
throw new ArgumentException(
|
||||
$"Failed to copy the incoming compressed buffer. Source buffer length: {decompressedData.Length}, start position: {0}, length: {decompressedData.Length}, destination buffer length: {Buffer.Length}.", e);
|
||||
}
|
||||
|
||||
lengthBits = decompressedData.Length * 8;
|
||||
DebugConsole.Log("Decompressing message: " + inLength + " to " + LengthBytes);
|
||||
DebugConsole.Log("Decompressing message: " + byteLength + " to " + LengthBytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
buf = new byte[inBuf.Length];
|
||||
Buffer = new byte[inBuf.Length];
|
||||
try
|
||||
{
|
||||
Array.Copy(inBuf, startPos, buf, 0, inLength);
|
||||
Array.Copy(inBuf, startPos, Buffer, 0, byteLength);
|
||||
}
|
||||
catch (ArgumentException e)
|
||||
{
|
||||
throw new ArgumentException($"Failed to copy the incoming uncompressed buffer. Source buffer length: {inBuf.Length}, start position: {startPos}, length: {inLength}, destination buffer length: {buf.Length}.", e);
|
||||
throw new ArgumentException($"Failed to copy the incoming uncompressed buffer. Source buffer length: {inBuf.Length}, start position: {startPos}, length: {byteLength}, destination buffer length: {Buffer.Length}.", e);
|
||||
}
|
||||
lengthBits = inLength * 8;
|
||||
|
||||
lengthBits = byteLength * 8;
|
||||
}
|
||||
|
||||
seekPos = 0;
|
||||
}
|
||||
|
||||
public bool ReadBoolean()
|
||||
{
|
||||
return MsgReader.ReadBoolean(buf, ref seekPos);
|
||||
return MsgReader.ReadBoolean(Buffer, ref seekPos);
|
||||
}
|
||||
|
||||
public void ReadPadBits()
|
||||
{
|
||||
MsgReader.ReadPadBits(buf, ref seekPos);
|
||||
}
|
||||
public void ReadPadBits() { MsgReader.ReadPadBits(ref seekPos); }
|
||||
|
||||
public byte ReadByte()
|
||||
{
|
||||
return MsgReader.ReadByte(buf, ref seekPos);
|
||||
return MsgReader.ReadByte(Buffer, ref seekPos);
|
||||
}
|
||||
|
||||
public byte PeekByte()
|
||||
{
|
||||
return MsgReader.PeekByte(Buffer, ref seekPos);
|
||||
}
|
||||
|
||||
public UInt16 ReadUInt16()
|
||||
{
|
||||
return MsgReader.ReadUInt16(buf, ref seekPos);
|
||||
return MsgReader.ReadUInt16(Buffer, ref seekPos);
|
||||
}
|
||||
|
||||
public Int16 ReadInt16()
|
||||
{
|
||||
return MsgReader.ReadInt16(buf, ref seekPos);
|
||||
return MsgReader.ReadInt16(Buffer, ref seekPos);
|
||||
}
|
||||
|
||||
public UInt32 ReadUInt32()
|
||||
{
|
||||
return MsgReader.ReadUInt32(buf, ref seekPos);
|
||||
return MsgReader.ReadUInt32(Buffer, ref seekPos);
|
||||
}
|
||||
|
||||
public Int32 ReadInt32()
|
||||
{
|
||||
return MsgReader.ReadInt32(buf, ref seekPos);
|
||||
return MsgReader.ReadInt32(Buffer, ref seekPos);
|
||||
}
|
||||
|
||||
public UInt64 ReadUInt64()
|
||||
{
|
||||
return MsgReader.ReadUInt64(buf, ref seekPos);
|
||||
return MsgReader.ReadUInt64(Buffer, ref seekPos);
|
||||
}
|
||||
|
||||
public Int64 ReadInt64()
|
||||
{
|
||||
return MsgReader.ReadInt64(buf, ref seekPos);
|
||||
return MsgReader.ReadInt64(Buffer, ref seekPos);
|
||||
}
|
||||
|
||||
public Single ReadSingle()
|
||||
{
|
||||
return MsgReader.ReadSingle(buf, ref seekPos);
|
||||
return MsgReader.ReadSingle(Buffer, ref seekPos);
|
||||
}
|
||||
|
||||
public Double ReadDouble()
|
||||
{
|
||||
return MsgReader.ReadDouble(buf, ref seekPos);
|
||||
return MsgReader.ReadDouble(Buffer, ref seekPos);
|
||||
}
|
||||
|
||||
public UInt32 ReadVariableUInt32()
|
||||
{
|
||||
return MsgReader.ReadVariableUInt32(buf, ref seekPos);
|
||||
return MsgReader.ReadVariableUInt32(Buffer, ref seekPos);
|
||||
}
|
||||
|
||||
public String ReadString()
|
||||
{
|
||||
return MsgReader.ReadString(buf, ref seekPos);
|
||||
return MsgReader.ReadString(Buffer, ref seekPos);
|
||||
}
|
||||
|
||||
public Identifier ReadIdentifier()
|
||||
@@ -777,35 +740,35 @@ namespace Barotrauma.Networking
|
||||
|
||||
public Color ReadColorR8G8B8()
|
||||
{
|
||||
return MsgReader.ReadColorR8G8B8(buf, ref seekPos);
|
||||
return MsgReader.ReadColorR8G8B8(Buffer, ref seekPos);
|
||||
}
|
||||
|
||||
|
||||
public Color ReadColorR8G8B8A8()
|
||||
{
|
||||
return MsgReader.ReadColorR8G8B8A8(buf, ref seekPos);
|
||||
return MsgReader.ReadColorR8G8B8A8(Buffer, ref seekPos);
|
||||
}
|
||||
|
||||
public int ReadRangedInteger(int min, int max)
|
||||
{
|
||||
return MsgReader.ReadRangedInteger(buf, ref seekPos, min, max);
|
||||
return MsgReader.ReadRangedInteger(Buffer, ref seekPos, min, max);
|
||||
}
|
||||
|
||||
public Single ReadRangedSingle(Single min, Single max, int bitCount)
|
||||
{
|
||||
return MsgReader.ReadRangedSingle(buf, ref seekPos, min, max, bitCount);
|
||||
return MsgReader.ReadRangedSingle(Buffer, ref seekPos, min, max, bitCount);
|
||||
}
|
||||
|
||||
public byte[] ReadBytes(int numberOfBytes)
|
||||
{
|
||||
return MsgReader.ReadBytes(buf, ref seekPos, numberOfBytes);
|
||||
return MsgReader.ReadBytes(Buffer, ref seekPos, numberOfBytes);
|
||||
}
|
||||
}
|
||||
|
||||
public class ReadWriteMessage : IWriteMessage, IReadMessage
|
||||
internal sealed class ReadWriteMessage : IWriteMessage, IReadMessage
|
||||
{
|
||||
private byte[] buf;
|
||||
private int seekPos = 0;
|
||||
private int lengthBits = 0;
|
||||
private int seekPos;
|
||||
private int lengthBits;
|
||||
|
||||
public ReadWriteMessage()
|
||||
{
|
||||
@@ -814,40 +777,22 @@ namespace Barotrauma.Networking
|
||||
lengthBits = 0;
|
||||
}
|
||||
|
||||
public ReadWriteMessage(byte[] b, int sPos, int lBits, bool copyBuf)
|
||||
public ReadWriteMessage(byte[] b, int bitPos, int lBits, bool copyBuf)
|
||||
{
|
||||
buf = copyBuf ? (byte[])b.Clone() : b;
|
||||
seekPos = sPos;
|
||||
seekPos = bitPos;
|
||||
lengthBits = lBits;
|
||||
}
|
||||
|
||||
public int BitPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return seekPos;
|
||||
}
|
||||
set
|
||||
{
|
||||
seekPos = value;
|
||||
}
|
||||
get => seekPos;
|
||||
set => seekPos = value;
|
||||
}
|
||||
|
||||
public int BytePosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return seekPos / 8;
|
||||
}
|
||||
}
|
||||
public int BytePosition => seekPos / 8;
|
||||
|
||||
public byte[] Buffer
|
||||
{
|
||||
get
|
||||
{
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
public byte[] Buffer => buf;
|
||||
|
||||
public int LengthBits
|
||||
{
|
||||
@@ -863,17 +808,11 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
public int LengthBytes
|
||||
{
|
||||
get
|
||||
{
|
||||
return (LengthBits + 7) / 8;
|
||||
}
|
||||
}
|
||||
public int LengthBytes => (LengthBits + 7) / 8;
|
||||
|
||||
public NetworkConnection Sender { get { return null; } }
|
||||
public NetworkConnection Sender => null;
|
||||
|
||||
public void Write(bool val)
|
||||
public void WriteBoolean(bool val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
@@ -883,47 +822,47 @@ namespace Barotrauma.Networking
|
||||
MsgWriter.WritePadBits(ref buf, ref seekPos);
|
||||
}
|
||||
|
||||
public void Write(byte val)
|
||||
public void WriteByte(byte val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(UInt16 val)
|
||||
public void WriteUInt16(UInt16 val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(Int16 val)
|
||||
public void WriteInt16(Int16 val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(UInt32 val)
|
||||
public void WriteUInt32(UInt32 val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(Int32 val)
|
||||
public void WriteInt32(Int32 val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(UInt64 val)
|
||||
public void WriteUInt64(UInt64 val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(Int64 val)
|
||||
public void WriteInt64(Int64 val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(Single val)
|
||||
public void WriteSingle(Single val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(Double val)
|
||||
public void WriteDouble(Double val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
@@ -932,7 +871,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
MsgWriter.WriteColorR8G8B8(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
|
||||
public void WriteColorR8G8B8A8(Color val)
|
||||
{
|
||||
MsgWriter.WriteColorR8G8B8A8(ref buf, ref seekPos, val);
|
||||
@@ -943,14 +882,14 @@ namespace Barotrauma.Networking
|
||||
MsgWriter.WriteVariableUInt32(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(String val)
|
||||
public void WriteString(String val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(Identifier val)
|
||||
public void WriteIdentifier(Identifier val)
|
||||
{
|
||||
Write(val.Value);
|
||||
WriteString(val.Value);
|
||||
}
|
||||
|
||||
public void WriteRangedInteger(int val, int min, int max)
|
||||
@@ -963,7 +902,7 @@ namespace Barotrauma.Networking
|
||||
MsgWriter.WriteRangedSingle(ref buf, ref seekPos, val, min, max, bitCount);
|
||||
}
|
||||
|
||||
public void Write(byte[] val, int startPos, int length)
|
||||
public void WriteBytes(byte[] val, int startPos, int length)
|
||||
{
|
||||
MsgWriter.WriteBytes(ref buf, ref seekPos, val, startPos, length);
|
||||
}
|
||||
@@ -973,16 +912,18 @@ namespace Barotrauma.Networking
|
||||
return MsgReader.ReadBoolean(buf, ref seekPos);
|
||||
}
|
||||
|
||||
public void ReadPadBits()
|
||||
{
|
||||
MsgReader.ReadPadBits(buf, ref seekPos);
|
||||
}
|
||||
public void ReadPadBits() { MsgReader.ReadPadBits(ref seekPos); }
|
||||
|
||||
public byte ReadByte()
|
||||
{
|
||||
return MsgReader.ReadByte(buf, ref seekPos);
|
||||
}
|
||||
|
||||
public byte PeekByte()
|
||||
{
|
||||
return MsgReader.PeekByte(buf, ref seekPos);
|
||||
}
|
||||
|
||||
public UInt16 ReadUInt16()
|
||||
{
|
||||
return MsgReader.ReadUInt16(buf, ref seekPos);
|
||||
@@ -1042,7 +983,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
return MsgReader.ReadColorR8G8B8(buf, ref seekPos);
|
||||
}
|
||||
|
||||
|
||||
public Color ReadColorR8G8B8A8()
|
||||
{
|
||||
return MsgReader.ReadColorR8G8B8A8(buf, ref seekPos);
|
||||
@@ -1063,9 +1004,10 @@ namespace Barotrauma.Networking
|
||||
return MsgReader.ReadBytes(buf, ref seekPos, numberOfBytes);
|
||||
}
|
||||
|
||||
public void PrepareForSending(ref byte[] outBuf, bool compressPastThreshold, out bool isCompressed, out int outLength)
|
||||
public byte[] PrepareForSending(bool compressPastThreshold, out bool isCompressed, out int outLength)
|
||||
{
|
||||
throw new InvalidOperationException("ReadWriteMessages are not to be sent");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-45
@@ -1,55 +1,14 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using Lidgren.Network;
|
||||
using Lidgren.Network;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
public class LidgrenConnection : NetworkConnection
|
||||
sealed class LidgrenConnection : NetworkConnection
|
||||
{
|
||||
public NetConnection NetConnection { get; private set; }
|
||||
public readonly NetConnection NetConnection;
|
||||
|
||||
public IPEndPoint IPEndPoint => NetConnection.RemoteEndPoint;
|
||||
|
||||
public string IPString
|
||||
public LidgrenConnection(NetConnection netConnection) : base(new LidgrenEndpoint(netConnection.RemoteEndPoint))
|
||||
{
|
||||
get
|
||||
{
|
||||
return IPEndPoint.Address.IsIPv4MappedToIPv6 ? IPEndPoint.Address.MapToIPv4NoThrow().ToString() : IPEndPoint.Address.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public UInt16 Port
|
||||
{
|
||||
get
|
||||
{
|
||||
return (UInt16)IPEndPoint.Port;
|
||||
}
|
||||
}
|
||||
|
||||
public LidgrenConnection(string name, NetConnection netConnection, UInt64 steamId)
|
||||
{
|
||||
Name = name;
|
||||
NetConnection = netConnection;
|
||||
SteamID = steamId;
|
||||
EndPointString = IPString;
|
||||
}
|
||||
|
||||
public override bool SetSteamIDIfUnknown(UInt64 id)
|
||||
{
|
||||
if (SteamID != 0) { return false; } //do not allow the SteamID to be set multiple times
|
||||
SteamID = id;
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool EndpointMatches(string endPoint)
|
||||
{
|
||||
if (IPEndPoint?.Address == null) { return false; }
|
||||
if (!IPAddress.TryParse(endPoint, out IPAddress addr)) { return false; }
|
||||
|
||||
IPAddress ip1 = IPEndPoint.Address.IsIPv4MappedToIPv6 ? IPEndPoint.Address.MapToIPv4() : IPEndPoint.Address;
|
||||
IPAddress ip2 = addr.IsIPv4MappedToIPv6 ? addr.MapToIPv4() : addr;
|
||||
|
||||
return ip1.ToString() == ip2.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+16
-36
@@ -8,57 +8,37 @@ namespace Barotrauma.Networking
|
||||
Disconnected = 0x2
|
||||
}
|
||||
|
||||
public abstract class NetworkConnection
|
||||
abstract class NetworkConnection
|
||||
{
|
||||
public const double TimeoutThreshold = 60.0; //full minute for timeout because loading screens can take quite a while
|
||||
public const double TimeoutThresholdInGame = 10.0;
|
||||
|
||||
public string Name;
|
||||
public AccountInfo AccountInfo { get; private set; } = AccountInfo.None;
|
||||
|
||||
public UInt64 SteamID
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public UInt64 OwnerSteamID
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public string EndPointString
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
public readonly Endpoint Endpoint;
|
||||
|
||||
[Obsolete("TODO: this doesn't belong in layer 1")]
|
||||
public LanguageIdentifier Language
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public abstract bool EndpointMatches(string endPoint);
|
||||
public NetworkConnection(Endpoint endpoint)
|
||||
{
|
||||
Endpoint = endpoint;
|
||||
}
|
||||
|
||||
public bool EndpointMatches(Endpoint endPoint)
|
||||
=> Endpoint == endPoint;
|
||||
|
||||
public NetworkConnectionStatus Status = NetworkConnectionStatus.Disconnected;
|
||||
|
||||
public virtual bool SetSteamIDIfUnknown(UInt64 id)
|
||||
public void SetAccountInfo(AccountInfo newInfo)
|
||||
{
|
||||
//by default, don't allow setting the ID, this is only done
|
||||
//with Lidgren connections since those are initialized before
|
||||
//the SteamID can be known; it's set once the Steam auth ticket
|
||||
//is received by the server.
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool SetOwnerSteamIDIfUnknown(UInt64 id)
|
||||
{
|
||||
//we know that for both Lidgren and SteamP2P, the
|
||||
//owner id isn't known until the auth ticket is
|
||||
//processed, so this method is the same for both
|
||||
if (OwnerSteamID != 0) { return false; }
|
||||
OwnerSteamID = id;
|
||||
return true;
|
||||
AccountInfo = newInfo;
|
||||
}
|
||||
|
||||
public sealed override string ToString()
|
||||
=> Endpoint.StringRepresentation;
|
||||
}
|
||||
}
|
||||
|
||||
+23
-9
@@ -1,19 +1,33 @@
|
||||
using Barotrauma.Steam;
|
||||
#nullable enable
|
||||
using System;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
public class PipeConnection : NetworkConnection
|
||||
sealed class PipeEndpoint : Endpoint
|
||||
{
|
||||
public PipeConnection(ulong steamId)
|
||||
{
|
||||
EndPointString = "PIPE";
|
||||
SteamID = steamId;
|
||||
}
|
||||
public override string StringRepresentation => "PIPE";
|
||||
|
||||
public override LocalizedString ServerTypeString => throw new InvalidOperationException();
|
||||
|
||||
public override bool EndpointMatches(string endPoint)
|
||||
public PipeEndpoint() : base(new PipeAddress()) { }
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is PipeEndpoint;
|
||||
|
||||
public override int GetHashCode() => 1;
|
||||
|
||||
public static bool operator ==(PipeEndpoint a, PipeEndpoint b)
|
||||
=> true;
|
||||
|
||||
public static bool operator !=(PipeEndpoint a, PipeEndpoint b)
|
||||
=> !(a == b);
|
||||
}
|
||||
|
||||
sealed class PipeConnection : NetworkConnection
|
||||
{
|
||||
public PipeConnection(AccountId accountId) : base(new PipeEndpoint())
|
||||
{
|
||||
return SteamManager.SteamIDStringToUInt64(endPoint) == SteamID || endPoint == "PIPE";
|
||||
SetAccountInfo(new AccountInfo(Option<AccountId>.Some(accountId)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-15
@@ -1,18 +1,13 @@
|
||||
using Barotrauma.Steam;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
public class SteamP2PConnection : NetworkConnection
|
||||
sealed class SteamP2PConnection : NetworkConnection
|
||||
{
|
||||
public double Timeout = 0.0;
|
||||
|
||||
public SteamP2PConnection(string name, UInt64 steamId)
|
||||
public SteamP2PConnection(SteamId steamId) : this(new SteamP2PEndpoint(steamId)) { }
|
||||
|
||||
public SteamP2PConnection(SteamP2PEndpoint endpoint) : base(endpoint)
|
||||
{
|
||||
SteamID = steamId;
|
||||
OwnerSteamID = 0;
|
||||
EndPointString = SteamManager.SteamIDUInt64ToString(SteamID);
|
||||
Name = name;
|
||||
Heartbeat();
|
||||
}
|
||||
|
||||
@@ -25,10 +20,5 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
Timeout = TimeoutThreshold;
|
||||
}
|
||||
|
||||
public override bool EndpointMatches(string endPoint)
|
||||
{
|
||||
return SteamManager.SteamIDStringToUInt64(endPoint) == SteamID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -40,6 +40,7 @@ namespace Barotrauma.Networking
|
||||
public static bool IsCompressed(this PacketHeader h)
|
||||
=> h.HasFlag(PacketHeader.IsCompressed);
|
||||
|
||||
#warning TODO: remove?
|
||||
public static bool IsConnectionInitializationStep(this PacketHeader h)
|
||||
=> h.HasFlag(PacketHeader.IsConnectionInitializationStep);
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Lidgren.Network;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
internal static class WriteOnlyMessageExtensions
|
||||
{
|
||||
#if CLIENT
|
||||
public static IWriteMessage WithHeader(this IWriteMessage msg, ClientPacketHeader header)
|
||||
{
|
||||
msg.WriteByte((byte)header);
|
||||
return msg;
|
||||
}
|
||||
#elif SERVER
|
||||
public static IWriteMessage WithHeader(this IWriteMessage msg, ServerPacketHeader header)
|
||||
{
|
||||
msg.WriteByte((byte)header);
|
||||
return msg;
|
||||
}
|
||||
#endif
|
||||
public static void WriteNetSerializableStruct(this IWriteMessage msg, INetSerializableStruct serializableStruct)
|
||||
{
|
||||
serializableStruct.Write(msg);
|
||||
}
|
||||
|
||||
public static NetOutgoingMessage ToLidgren(this IWriteMessage msg, NetPeer peer)
|
||||
{
|
||||
NetOutgoingMessage outMsg = peer.CreateMessage();
|
||||
outMsg.Write(msg.Buffer, 0, msg.LengthBytes);
|
||||
return outMsg;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class NetIncomingMessageExtensions
|
||||
{
|
||||
public static T ReadHeader<T>(this NetIncomingMessage msg) where T : Enum
|
||||
{
|
||||
byte header = msg.ReadByte();
|
||||
return Unsafe.As<byte, T>(ref header);
|
||||
}
|
||||
|
||||
public static IReadMessage ToReadMessage(this NetIncomingMessage msg)
|
||||
{
|
||||
return new ReadWriteMessage(msg.Data, 0, msg.LengthBits, copyBuf: false);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class DeliveryMethodExtensions
|
||||
{
|
||||
public static NetDeliveryMethod ToLidgren(this DeliveryMethod deliveryMethod) =>
|
||||
deliveryMethod switch
|
||||
{
|
||||
DeliveryMethod.Unreliable => NetDeliveryMethod.Unreliable,
|
||||
DeliveryMethod.Reliable => NetDeliveryMethod.ReliableUnordered,
|
||||
DeliveryMethod.ReliableOrdered => NetDeliveryMethod.ReliableOrdered,
|
||||
_ => NetDeliveryMethod.Unreliable
|
||||
};
|
||||
|
||||
public static Steamworks.P2PSend ToSteam(this DeliveryMethod deliveryMethod) =>
|
||||
deliveryMethod switch
|
||||
{
|
||||
DeliveryMethod.Reliable => Steamworks.P2PSend.Reliable,
|
||||
DeliveryMethod.ReliableOrdered => Steamworks.P2PSend.Unreliable,
|
||||
_ => Steamworks.P2PSend.Unreliable
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
[NetworkSerialize]
|
||||
internal struct PeerPacketHeaders : INetSerializableStruct
|
||||
{
|
||||
public DeliveryMethod DeliveryMethod;
|
||||
public PacketHeader PacketHeader;
|
||||
public ConnectionInitialization? Initialization;
|
||||
|
||||
public readonly void Deconstruct(
|
||||
out DeliveryMethod deliveryMethod,
|
||||
out PacketHeader packetHeader,
|
||||
out ConnectionInitialization? initialization)
|
||||
{
|
||||
deliveryMethod = DeliveryMethod;
|
||||
packetHeader = PacketHeader;
|
||||
initialization = Initialization;
|
||||
}
|
||||
}
|
||||
|
||||
[NetworkSerialize(ArrayMaxSize = ushort.MaxValue)]
|
||||
internal struct ClientSteamTicketAndVersionPacket : INetSerializableStruct
|
||||
{
|
||||
public string Name;
|
||||
public Option<int> OwnerKey;
|
||||
|
||||
#warning TODO: do something about the type of this
|
||||
// It probably should be Option<SteamId> but we shouldn't build support for
|
||||
// writing SteamIDs to INetSerializableStruct; we should consider adding
|
||||
// attributes to give custom behaviors to specific members of a struct
|
||||
public Option<AccountId> SteamId;
|
||||
|
||||
public Option<byte[]> SteamAuthTicket;
|
||||
public string GameVersion;
|
||||
public Identifier Language;
|
||||
}
|
||||
|
||||
[NetworkSerialize]
|
||||
internal struct SteamP2PInitializationRelayPacket : INetSerializableStruct
|
||||
{
|
||||
public ulong LobbyID;
|
||||
public PeerPacketMessage Message;
|
||||
}
|
||||
|
||||
[NetworkSerialize]
|
||||
internal struct SteamP2PInitializationOwnerPacket : INetSerializableStruct
|
||||
{
|
||||
public string OwnerName;
|
||||
}
|
||||
|
||||
|
||||
[NetworkSerialize(ArrayMaxSize = ushort.MaxValue)]
|
||||
internal struct ServerPeerContentPackageOrderPacket : INetSerializableStruct
|
||||
{
|
||||
public string ServerName;
|
||||
public ImmutableArray<ServerContentPackage> ContentPackages;
|
||||
}
|
||||
|
||||
[NetworkSerialize(ArrayMaxSize = ushort.MaxValue)]
|
||||
internal struct PeerPacketMessage : INetSerializableStruct
|
||||
{
|
||||
public byte[] Buffer;
|
||||
public readonly int Length => Buffer.Length;
|
||||
|
||||
public readonly IReadMessage GetReadMessageUncompressed() => new ReadWriteMessage(Buffer, 0, Length, copyBuf: false);
|
||||
public readonly IReadMessage GetReadMessage(bool isCompressed, NetworkConnection conn) => new ReadOnlyMessage(Buffer, isCompressed, 0, Length, conn);
|
||||
}
|
||||
|
||||
[NetworkSerialize(ArrayMaxSize = byte.MaxValue)]
|
||||
internal struct ClientPeerPasswordPacket : INetSerializableStruct
|
||||
{
|
||||
public byte[] Password;
|
||||
}
|
||||
|
||||
[NetworkSerialize]
|
||||
internal struct ServerPeerPasswordPacket : INetSerializableStruct
|
||||
{
|
||||
public Option<int> Salt;
|
||||
public Option<int> RetriesLeft;
|
||||
}
|
||||
|
||||
[NetworkSerialize]
|
||||
internal readonly struct PeerDisconnectPacket : INetSerializableStruct
|
||||
{
|
||||
public readonly DisconnectReason DisconnectReason;
|
||||
|
||||
public readonly string AdditionalInformation;
|
||||
|
||||
private PeerDisconnectPacket(
|
||||
DisconnectReason disconnectReason,
|
||||
string additionalInformation = "")
|
||||
{
|
||||
DisconnectReason = disconnectReason;
|
||||
AdditionalInformation = additionalInformation;
|
||||
}
|
||||
|
||||
public LocalizedString ChatMessage(Client c)
|
||||
{
|
||||
LocalizedString message = DisconnectReason switch
|
||||
{
|
||||
DisconnectReason.Disconnected => TextManager.GetWithVariable("ServerMessage.ClientLeftServer",
|
||||
"[client]", c.Name),
|
||||
DisconnectReason.Banned => TextManager.GetWithVariable("servermessage.bannedfromserver", "[client]", c.Name),
|
||||
DisconnectReason.Kicked => TextManager.GetWithVariable("servermessage.kickedfromserver", "[client]", c.Name),
|
||||
_ => TextManager.GetWithVariables("ChatMsg.DisconnectedWithReason",
|
||||
("[client]", c.Name),
|
||||
("[reason]", TextManager.Get($"ChatMsg.DisconnectReason.{DisconnectReason}")))
|
||||
};
|
||||
if (!string.IsNullOrEmpty(AdditionalInformation) &&
|
||||
DisconnectReason is DisconnectReason.Banned or DisconnectReason.Kicked)
|
||||
{
|
||||
message += " "+ TextManager.Get("banreason") + " " + TextManager.GetServerMessage(AdditionalInformation);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
|
||||
private LocalizedString MsgWithReason
|
||||
=> TextManager.Get($"DisconnectReason.{DisconnectReason}")
|
||||
+ "\n\n"
|
||||
+ TextManager.Get("banreason") + " " + TextManager.GetServerMessage(AdditionalInformation);
|
||||
|
||||
private LocalizedString ServerMessage
|
||||
=> TextManager.Get($"ServerMessage.{DisconnectReason}");
|
||||
|
||||
public LocalizedString PopupMessage
|
||||
=> DisconnectReason switch
|
||||
{
|
||||
DisconnectReason.Banned => MsgWithReason,
|
||||
DisconnectReason.Kicked => MsgWithReason,
|
||||
DisconnectReason.InvalidVersion => TextManager.GetWithVariables("DisconnectMessage.InvalidVersion",
|
||||
("[version]", AdditionalInformation),
|
||||
("[clientversion]", GameMain.Version.ToString())),
|
||||
DisconnectReason.ExcessiveDesyncOldEvent => ServerMessage,
|
||||
DisconnectReason.ExcessiveDesyncRemovedEvent => ServerMessage,
|
||||
DisconnectReason.SyncTimeout => ServerMessage,
|
||||
_ => TextManager.Get($"DisconnectReason.{DisconnectReason}").Fallback(TextManager.Get("ConnectionLost"))
|
||||
};
|
||||
|
||||
public LocalizedString ReconnectMessage
|
||||
=> PopupMessage + "\n\n" + TextManager.Get("ConnectionLostReconnecting");
|
||||
|
||||
public PlayerConnectionChangeType ConnectionChangeType
|
||||
=> DisconnectReason switch
|
||||
{
|
||||
DisconnectReason.Banned => PlayerConnectionChangeType.Banned,
|
||||
DisconnectReason.Kicked => PlayerConnectionChangeType.Kicked,
|
||||
_ => PlayerConnectionChangeType.Disconnected
|
||||
};
|
||||
|
||||
public bool ShouldAttemptReconnect
|
||||
=> DisconnectReason
|
||||
is DisconnectReason.ExcessiveDesyncOldEvent
|
||||
or DisconnectReason.ExcessiveDesyncRemovedEvent
|
||||
or DisconnectReason.Timeout
|
||||
or DisconnectReason.SyncTimeout
|
||||
or DisconnectReason.SteamP2PTimeOut;
|
||||
|
||||
public bool IsEventSyncError
|
||||
=> DisconnectReason
|
||||
is DisconnectReason.ExcessiveDesyncOldEvent
|
||||
or DisconnectReason.ExcessiveDesyncRemovedEvent
|
||||
or DisconnectReason.SyncTimeout;
|
||||
|
||||
public bool ShouldCreateAnalyticsEvent
|
||||
=> DisconnectReason is not (
|
||||
DisconnectReason.Disconnected
|
||||
or DisconnectReason.Banned
|
||||
or DisconnectReason.Kicked
|
||||
or DisconnectReason.TooManyFailedLogins
|
||||
or DisconnectReason.InvalidVersion);
|
||||
|
||||
private const string lidgrenSeparator = ":hankey:";
|
||||
|
||||
/// <summary>
|
||||
/// This exists because Lidgren is a piece of shit and
|
||||
/// doesn't readily support sending anything other than
|
||||
/// a string through a disconnect packet, so this thing
|
||||
/// needs a sufficiently nasty string representation that
|
||||
/// can be decoded with some certainty that it won't get
|
||||
/// mangled by user input.
|
||||
/// </summary>
|
||||
public string ToLidgrenStringRepresentation()
|
||||
{
|
||||
static string strToBase64(string str)
|
||||
=> Convert.ToBase64String(Encoding.UTF8.GetBytes(str));
|
||||
|
||||
return DisconnectReason
|
||||
+ lidgrenSeparator
|
||||
+ strToBase64(AdditionalInformation);
|
||||
}
|
||||
|
||||
public static Option<PeerDisconnectPacket> FromLidgrenStringRepresentation(string str)
|
||||
{
|
||||
// Lidgren has some hardcoded disconnect strings that it uses
|
||||
// when it detects that a connection has failed. We can handle
|
||||
// timeouts, so let's look for strings related to that and return
|
||||
// an appropriate PeerDisconnectPacket.
|
||||
switch (str)
|
||||
{
|
||||
case Lidgren.Network.NetConnection.NoResponseMessage:
|
||||
case "Connection timed out":
|
||||
case "Reconnecting":
|
||||
return Option<PeerDisconnectPacket>.Some(WithReason(DisconnectReason.Timeout));
|
||||
}
|
||||
|
||||
static string base64ToStr(string base64)
|
||||
=> Encoding.UTF8.GetString(Convert.FromBase64String(base64));
|
||||
|
||||
string[] split = str.Split(lidgrenSeparator);
|
||||
if (split.Length != 2) { return Option<PeerDisconnectPacket>.None(); }
|
||||
if (!Enum.TryParse(split[0], out DisconnectReason disconnectReason)) { return Option<PeerDisconnectPacket>.None(); }
|
||||
return Option<PeerDisconnectPacket>.Some(new PeerDisconnectPacket(disconnectReason, base64ToStr(split[1])));
|
||||
}
|
||||
|
||||
public static PeerDisconnectPacket Custom(string customMessage)
|
||||
=> new PeerDisconnectPacket(
|
||||
DisconnectReason.Unknown,
|
||||
customMessage);
|
||||
|
||||
public static PeerDisconnectPacket WithReason(DisconnectReason disconnectReason)
|
||||
=> new PeerDisconnectPacket(disconnectReason);
|
||||
|
||||
public static PeerDisconnectPacket Kicked(string? msg)
|
||||
=> new PeerDisconnectPacket(DisconnectReason.Kicked, msg ?? "");
|
||||
|
||||
public static PeerDisconnectPacket Banned(string? msg)
|
||||
=> new PeerDisconnectPacket(DisconnectReason.Banned, msg ?? "");
|
||||
|
||||
public static PeerDisconnectPacket InvalidVersion()
|
||||
=> new PeerDisconnectPacket(
|
||||
DisconnectReason.InvalidVersion,
|
||||
GameMain.Version.ToString());
|
||||
|
||||
public static PeerDisconnectPacket SteamP2PError(Steamworks.P2PSessionError error)
|
||||
=> new PeerDisconnectPacket(
|
||||
DisconnectReason.SteamP2PError,
|
||||
error.ToString());
|
||||
|
||||
public static PeerDisconnectPacket SteamAuthError(Steamworks.BeginAuthResult error)
|
||||
=> new PeerDisconnectPacket(
|
||||
DisconnectReason.SteamAuthenticationFailed,
|
||||
$"{nameof(Steamworks.BeginAuthResult)}.{error}");
|
||||
|
||||
public static PeerDisconnectPacket SteamAuthError(Steamworks.AuthResponse error)
|
||||
=> new PeerDisconnectPacket(
|
||||
DisconnectReason.SteamAuthenticationFailed,
|
||||
$"{nameof(Steamworks.AuthResponse)}.{error}");
|
||||
}
|
||||
|
||||
// ReSharper disable MemberCanBePrivate.Global, FieldCanBeMadeReadOnly.Global, UnassignedField.Global
|
||||
public sealed class ServerContentPackage : INetSerializableStruct
|
||||
{
|
||||
[NetworkSerialize]
|
||||
public string Name = "";
|
||||
|
||||
[NetworkSerialize(ArrayMaxSize = ushort.MaxValue)]
|
||||
public byte[] HashBytes = Array.Empty<byte>();
|
||||
|
||||
[NetworkSerialize]
|
||||
public string UgcId = "";
|
||||
|
||||
[NetworkSerialize]
|
||||
public uint InstallTimeDiffInSeconds;
|
||||
|
||||
[NetworkSerialize]
|
||||
public bool IsMandatory;
|
||||
|
||||
private Md5Hash? cachedHash;
|
||||
private DateTime? cachedDateTime;
|
||||
|
||||
public Md5Hash Hash
|
||||
{
|
||||
get => cachedHash ??= Md5Hash.BytesAsHash(HashBytes);
|
||||
set
|
||||
{
|
||||
cachedHash = value;
|
||||
HashBytes = value.ByteRepresentation;
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime InstallTime => cachedDateTime ??= DateTime.UtcNow + TimeSpan.FromSeconds(InstallTimeDiffInSeconds);
|
||||
public RegularPackage? RegularPackage => ContentPackageManager.RegularPackages.FirstOrDefault(p => p.Hash.Equals(Hash));
|
||||
public CorePackage? CorePackage => ContentPackageManager.CorePackages.FirstOrDefault(p => p.Hash.Equals(Hash));
|
||||
public ContentPackage? ContentPackage => (ContentPackage?)RegularPackage ?? CorePackage;
|
||||
|
||||
public ServerContentPackage() { }
|
||||
|
||||
public ServerContentPackage(ContentPackage contentPackage, DateTime referenceTime)
|
||||
{
|
||||
Name = contentPackage.Name;
|
||||
Hash = contentPackage.Hash;
|
||||
UgcId = contentPackage.UgcId.TryUnwrap(out var ugcId)
|
||||
? ugcId.StringRepresentation
|
||||
: "";
|
||||
IsMandatory = !contentPackage.Files.All(f => f is SubmarineFile);
|
||||
InstallTimeDiffInSeconds =
|
||||
contentPackage.InstallTime.TryUnwrap(out var installTime)
|
||||
? (uint)(installTime - referenceTime).TotalSeconds
|
||||
: 0;
|
||||
}
|
||||
|
||||
public string GetPackageStr() => $"\"{Name}\" (hash {Hash.ShortRepresentation})";
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,11 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
partial class RespawnManager : Entity, IServerSerializable
|
||||
{
|
||||
/// <summary>
|
||||
/// How much skills drop towards the job's default skill levels when dying
|
||||
/// </summary>
|
||||
const float SkillReductionOnDeath = 0.75f;
|
||||
|
||||
public enum State
|
||||
{
|
||||
Waiting,
|
||||
@@ -285,6 +290,7 @@ namespace Barotrauma.Networking
|
||||
#endif
|
||||
}
|
||||
}
|
||||
respawnItems.Clear();
|
||||
|
||||
foreach (Structure wall in Structure.WallList)
|
||||
{
|
||||
@@ -343,10 +349,14 @@ namespace Barotrauma.Networking
|
||||
RespawnCharactersProjSpecific(shuttlePos);
|
||||
}
|
||||
|
||||
public static AfflictionPrefab GetRespawnPenaltyAfflictionPrefab()
|
||||
{
|
||||
return AfflictionPrefab.Prefabs.First(a => a.AfflictionType == "respawnpenalty");
|
||||
}
|
||||
|
||||
public static Affliction GetRespawnPenaltyAffliction()
|
||||
{
|
||||
var respawnPenaltyAffliction = AfflictionPrefab.Prefabs.First(a => a.AfflictionType == "respawnpenalty");
|
||||
return respawnPenaltyAffliction?.Instantiate(10.0f);
|
||||
return GetRespawnPenaltyAfflictionPrefab()?.Instantiate(10.0f);
|
||||
}
|
||||
|
||||
public static void GiveRespawnPenaltyAffliction(Character character)
|
||||
|
||||
@@ -71,27 +71,18 @@ namespace Barotrauma.Networking
|
||||
|
||||
public class SavedClientPermission
|
||||
{
|
||||
public readonly string EndPoint;
|
||||
public readonly ulong SteamID;
|
||||
public readonly Either<Address, AccountId> AddressOrAccountId;
|
||||
public readonly string Name;
|
||||
public HashSet<DebugConsole.Command> PermittedCommands;
|
||||
public readonly ImmutableHashSet<DebugConsole.Command> PermittedCommands;
|
||||
|
||||
public ClientPermissions Permissions;
|
||||
public readonly ClientPermissions Permissions;
|
||||
|
||||
public SavedClientPermission(string name, string endpoint, ClientPermissions permissions, HashSet<DebugConsole.Command> permittedCommands)
|
||||
public SavedClientPermission(string name, Either<Address, AccountId> addressOrAccountId, ClientPermissions permissions, IEnumerable<DebugConsole.Command> permittedCommands)
|
||||
{
|
||||
this.Name = name;
|
||||
this.EndPoint = endpoint;
|
||||
this.AddressOrAccountId = addressOrAccountId;
|
||||
this.Permissions = permissions;
|
||||
this.PermittedCommands = permittedCommands;
|
||||
}
|
||||
public SavedClientPermission(string name, ulong steamID, ClientPermissions permissions, HashSet<DebugConsole.Command> permittedCommands)
|
||||
{
|
||||
this.Name = name;
|
||||
this.SteamID = steamID;
|
||||
|
||||
this.Permissions = permissions;
|
||||
this.PermittedCommands = permittedCommands;
|
||||
this.PermittedCommands = permittedCommands.ToImmutableHashSet();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,48 +210,48 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
case "float":
|
||||
msg.WriteVariableUInt32(4);
|
||||
msg.Write((float)overrideValue);
|
||||
msg.WriteSingle((float)overrideValue);
|
||||
break;
|
||||
case "int":
|
||||
msg.WriteVariableUInt32(4);
|
||||
msg.Write((int)overrideValue);
|
||||
msg.WriteInt32((int)overrideValue);
|
||||
break;
|
||||
case "vector2":
|
||||
msg.WriteVariableUInt32(8);
|
||||
msg.Write(((Vector2)overrideValue).X);
|
||||
msg.Write(((Vector2)overrideValue).Y);
|
||||
msg.WriteSingle(((Vector2)overrideValue).X);
|
||||
msg.WriteSingle(((Vector2)overrideValue).Y);
|
||||
break;
|
||||
case "vector3":
|
||||
msg.WriteVariableUInt32(12);
|
||||
msg.Write(((Vector3)overrideValue).X);
|
||||
msg.Write(((Vector3)overrideValue).Y);
|
||||
msg.Write(((Vector3)overrideValue).Z);
|
||||
msg.WriteSingle(((Vector3)overrideValue).X);
|
||||
msg.WriteSingle(((Vector3)overrideValue).Y);
|
||||
msg.WriteSingle(((Vector3)overrideValue).Z);
|
||||
break;
|
||||
case "vector4":
|
||||
msg.WriteVariableUInt32(16);
|
||||
msg.Write(((Vector4)overrideValue).X);
|
||||
msg.Write(((Vector4)overrideValue).Y);
|
||||
msg.Write(((Vector4)overrideValue).Z);
|
||||
msg.Write(((Vector4)overrideValue).W);
|
||||
msg.WriteSingle(((Vector4)overrideValue).X);
|
||||
msg.WriteSingle(((Vector4)overrideValue).Y);
|
||||
msg.WriteSingle(((Vector4)overrideValue).Z);
|
||||
msg.WriteSingle(((Vector4)overrideValue).W);
|
||||
break;
|
||||
case "color":
|
||||
msg.WriteVariableUInt32(4);
|
||||
msg.Write(((Color)overrideValue).R);
|
||||
msg.Write(((Color)overrideValue).G);
|
||||
msg.Write(((Color)overrideValue).B);
|
||||
msg.Write(((Color)overrideValue).A);
|
||||
msg.WriteByte(((Color)overrideValue).R);
|
||||
msg.WriteByte(((Color)overrideValue).G);
|
||||
msg.WriteByte(((Color)overrideValue).B);
|
||||
msg.WriteByte(((Color)overrideValue).A);
|
||||
break;
|
||||
case "rectangle":
|
||||
msg.WriteVariableUInt32(16);
|
||||
msg.Write(((Rectangle)overrideValue).X);
|
||||
msg.Write(((Rectangle)overrideValue).Y);
|
||||
msg.Write(((Rectangle)overrideValue).Width);
|
||||
msg.Write(((Rectangle)overrideValue).Height);
|
||||
msg.WriteInt32(((Rectangle)overrideValue).X);
|
||||
msg.WriteInt32(((Rectangle)overrideValue).Y);
|
||||
msg.WriteInt32(((Rectangle)overrideValue).Width);
|
||||
msg.WriteInt32(((Rectangle)overrideValue).Height);
|
||||
break;
|
||||
default:
|
||||
string strVal = overrideValue.ToString();
|
||||
|
||||
msg.Write(strVal);
|
||||
msg.WriteString(strVal);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -280,7 +271,6 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
ServerLog = new ServerLog(serverName);
|
||||
|
||||
Whitelist = new WhiteList();
|
||||
BanList = new BanList();
|
||||
|
||||
ExtraCargo = new Dictionary<ItemPrefab, int>();
|
||||
@@ -402,13 +392,12 @@ namespace Barotrauma.Networking
|
||||
|
||||
public List<SavedClientPermission> ClientPermissions { get; private set; } = new List<SavedClientPermission>();
|
||||
|
||||
public WhiteList Whitelist { get; private set; }
|
||||
|
||||
private int tickRate = 20;
|
||||
[Serialize(20, IsPropertySaveable.Yes)]
|
||||
public int TickRate
|
||||
{
|
||||
get;
|
||||
set;
|
||||
get { return tickRate; }
|
||||
set { tickRate = MathHelper.Clamp(value, 1, 60); }
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes)]
|
||||
@@ -566,7 +555,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
public bool HasPassword
|
||||
{
|
||||
get { return password != null; }
|
||||
get { return !string.IsNullOrEmpty(password); }
|
||||
#if CLIENT
|
||||
set
|
||||
{
|
||||
@@ -814,6 +803,13 @@ namespace Barotrauma.Networking
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(120.0f, IsPropertySaveable.Yes)]
|
||||
public float DisallowKickVoteTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(300.0f, IsPropertySaveable.Yes)]
|
||||
public float KillDisconnectedTime
|
||||
{
|
||||
@@ -962,14 +958,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
public void SetPassword(string password)
|
||||
{
|
||||
if (string.IsNullOrEmpty(password))
|
||||
{
|
||||
this.password = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.password = password;
|
||||
}
|
||||
this.password = string.IsNullOrEmpty(password) ? null : password;
|
||||
}
|
||||
|
||||
public static byte[] SaltPassword(byte[] password, int salt)
|
||||
@@ -986,14 +975,9 @@ namespace Barotrauma.Networking
|
||||
|
||||
public bool IsPasswordCorrect(byte[] input, int salt)
|
||||
{
|
||||
if (!HasPassword) return true;
|
||||
if (!HasPassword) { return true; }
|
||||
byte[] saltedPw = SaltPassword(Encoding.UTF8.GetBytes(password), salt);
|
||||
if (input.Length != saltedPw.Length) return false;
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
if (input[i] != saltedPw[i]) return false;
|
||||
}
|
||||
return true;
|
||||
return saltedPw.SequenceEqual(input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1048,7 +1032,7 @@ namespace Barotrauma.Networking
|
||||
msg.WriteVariableUInt32((uint)monsterNames.Count);
|
||||
foreach (Identifier s in monsterNames)
|
||||
{
|
||||
msg.Write(monsterEnabled[s]);
|
||||
msg.WriteBoolean(monsterEnabled[s]);
|
||||
}
|
||||
msg.WritePadBits();
|
||||
}
|
||||
@@ -1080,15 +1064,15 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (ExtraCargo == null)
|
||||
{
|
||||
msg.Write((UInt32)0);
|
||||
msg.WriteUInt32((UInt32)0);
|
||||
return;
|
||||
}
|
||||
|
||||
msg.Write((UInt32)ExtraCargo.Count);
|
||||
msg.WriteUInt32((UInt32)ExtraCargo.Count);
|
||||
foreach (KeyValuePair<ItemPrefab, int> kvp in ExtraCargo)
|
||||
{
|
||||
msg.Write(kvp.Key.Identifier);
|
||||
msg.Write((byte)kvp.Value);
|
||||
msg.WriteIdentifier(kvp.Key.Identifier);
|
||||
msg.WriteByte((byte)kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1118,7 +1102,7 @@ namespace Barotrauma.Networking
|
||||
msg.WriteVariableUInt32((uint)HiddenSubs.Count);
|
||||
foreach (string submarineName in HiddenSubs)
|
||||
{
|
||||
msg.Write((UInt16)subList.FindIndex(s => s.Name.Equals(submarineName, StringComparison.OrdinalIgnoreCase)));
|
||||
msg.WriteUInt16((UInt16)subList.FindIndex(s => s.Name.Equals(submarineName, StringComparison.OrdinalIgnoreCase)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
|
||||
public class VoipQueue : IDisposable
|
||||
class VoipQueue : IDisposable
|
||||
{
|
||||
public const int BUFFER_COUNT = 8;
|
||||
protected int[] bufferLengths;
|
||||
@@ -123,16 +119,16 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (!CanSend) { throw new Exception("Called Write on a VoipQueue not set up for sending"); }
|
||||
|
||||
msg.Write((UInt16)LatestBufferID);
|
||||
msg.Write(ForceLocal); msg.WritePadBits();
|
||||
msg.WriteUInt16((UInt16)LatestBufferID);
|
||||
msg.WriteBoolean(ForceLocal); msg.WritePadBits();
|
||||
lock (buffers)
|
||||
{
|
||||
for (int i = 0; i < BUFFER_COUNT; i++)
|
||||
{
|
||||
int index = (newestBufferInd + i + 1) % BUFFER_COUNT;
|
||||
|
||||
msg.Write((byte)bufferLengths[index]);
|
||||
msg.Write(buffers[index], 0, bufferLengths[index]);
|
||||
msg.WriteByte((byte)bufferLengths[index]);
|
||||
msg.WriteBytes(buffers[index], 0, bufferLengths[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Barotrauma
|
||||
{
|
||||
public enum VoteState { None = 0, Started = 1, Running = 2, Passed = 3, Failed = 4 };
|
||||
|
||||
private IReadOnlyDictionary<T, int> GetVoteCounts<T>(VoteType voteType, List<Client> voters)
|
||||
private IReadOnlyDictionary<T, int> GetVoteCounts<T>(VoteType voteType, IEnumerable<Client> voters)
|
||||
{
|
||||
Dictionary<T, int> voteList = new Dictionary<T, int>();
|
||||
|
||||
@@ -56,23 +56,5 @@ namespace Barotrauma
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
public void ResetVotes(List<Client> connectedClients)
|
||||
{
|
||||
foreach (Client client in connectedClients)
|
||||
{
|
||||
client.ResetVotes();
|
||||
}
|
||||
#if CLIENT
|
||||
foreach (VoteType voteType in Enum.GetValues(typeof(VoteType)))
|
||||
{
|
||||
SetVoteCountYes(voteType, 0);
|
||||
SetVoteCountNo(voteType, 0);
|
||||
SetVoteCountMax(voteType, 0);
|
||||
}
|
||||
UpdateVoteTexts(connectedClients, VoteType.Mode);
|
||||
UpdateVoteTexts(connectedClients, VoteType.Sub);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
partial class WhiteListedPlayer
|
||||
{
|
||||
public string Name;
|
||||
public string IP;
|
||||
|
||||
public UInt16 UniqueIdentifier;
|
||||
}
|
||||
|
||||
partial class WhiteList
|
||||
{
|
||||
const string SavePath = "Data/whitelist.txt";
|
||||
|
||||
private List<WhiteListedPlayer> whitelistedPlayers;
|
||||
public List<WhiteListedPlayer> WhiteListedPlayers
|
||||
{
|
||||
get { return whitelistedPlayers; }
|
||||
}
|
||||
|
||||
public bool Enabled;
|
||||
|
||||
partial void InitProjSpecific();
|
||||
public WhiteList()
|
||||
{
|
||||
Enabled = false;
|
||||
whitelistedPlayers = new List<WhiteListedPlayer>();
|
||||
|
||||
InitProjSpecific();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user