Unstable v0.19.1.0

This commit is contained in:
Juan Pablo Arce
2022-08-19 13:59:08 -03:00
parent 6b55adcdd9
commit 1219615d64
192 changed files with 3875 additions and 2648 deletions
@@ -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>(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>(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);
}
}
@@ -0,0 +1,69 @@
#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)
{
NetAddress = netAddress;
}
public new static Option<LidgrenAddress> Parse(string endpointStr)
{
if (IPAddress.TryParse(endpointStr, out IPAddress? netEndpoint))
{
return Option<LidgrenAddress>.Some(new LidgrenAddress(netEndpoint!));
}
try
{
var resolvedAddresses = Dns.GetHostAddresses(endpointStr);
return resolvedAddresses.Any()
? Option<LidgrenAddress>.Some(new LidgrenAddress(resolvedAddresses.First()))
: Option<LidgrenAddress>.None();
}
catch (SocketException)
{
return Option<LidgrenAddress>.None();
}
catch (ArgumentOutOfRangeException)
{
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);
}
}
@@ -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);
}
}
@@ -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,33 @@
#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>(str);
public static bool operator ==(Endpoint a, Endpoint b)
=> a.Equals(b);
public static bool operator !=(Endpoint a, Endpoint b)
=> !(a == b);
}
}
@@ -0,0 +1,63 @@
#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)
{
if (IPEndPoint.TryParse(endpointStr, out IPEndPoint? netEndpoint))
{
return Option<LidgrenEndpoint>.Some(new LidgrenEndpoint(netEndpoint!));
}
if (endpointStr.Count(c => c == ':') == 1)
{
string[] split = endpointStr.Split(':');
string hostName = split[0];
if (LidgrenAddress.Parse(hostName).TryUnwrap(out var adr)
&& int.TryParse(split[1], out var port))
{
return Option<LidgrenEndpoint>.Some(new LidgrenEndpoint(adr.NetAddress, port));
}
}
return LidgrenAddress.Parse(endpointStr)
.Select(adr => new LidgrenEndpoint(adr.NetAddress, NetConfig.DefaultPort));
}
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);
}
}
@@ -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);
}
}
@@ -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();
@@ -2,7 +2,7 @@
namespace Barotrauma.Networking
{
public interface IWriteMessage
interface IWriteMessage
{
void Write(bool val);
void WritePadBits();
@@ -11,7 +11,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;
@@ -254,6 +254,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);
@@ -409,7 +415,7 @@ namespace Barotrauma.Networking
}
}
public class WriteOnlyMessage : IWriteMessage
class WriteOnlyMessage : IWriteMessage
{
private byte[] buf = new byte[MsgConstants.InitialBufferSize];
private int seekPos = 0;
@@ -602,9 +608,9 @@ namespace Barotrauma.Networking
}
}
public class ReadOnlyMessage : IReadMessage
class ReadOnlyMessage : IReadMessage
{
private byte[] buf;
private readonly byte[] buf;
private int seekPos = 0;
private int lengthBits = 0;
@@ -720,6 +726,11 @@ namespace Barotrauma.Networking
return MsgReader.ReadByte(buf, ref seekPos);
}
public byte PeekByte()
{
return MsgReader.PeekByte(buf, ref seekPos);
}
public UInt16 ReadUInt16()
{
return MsgReader.ReadUInt16(buf, ref seekPos);
@@ -801,7 +812,7 @@ namespace Barotrauma.Networking
}
}
public class ReadWriteMessage : IWriteMessage, IReadMessage
class ReadWriteMessage : IWriteMessage, IReadMessage
{
private byte[] buf;
private int seekPos = 0;
@@ -983,6 +994,11 @@ namespace Barotrauma.Networking
return MsgReader.ReadByte(buf, ref seekPos);
}
public byte PeekByte()
{
return MsgReader.PeekByte(buf, ref seekPos);
}
public UInt16 ReadUInt16()
{
return MsgReader.ReadUInt16(buf, ref seekPos);
@@ -1067,5 +1083,6 @@ namespace Barotrauma.Networking
{
throw new InvalidOperationException("ReadWriteMessages are not to be sent");
}
}
}
@@ -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();
}
}
}
@@ -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;
}
}
@@ -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)));
}
}
}
@@ -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;
}
}
}