Merge branch 'master' of https://github.com/Regalis11/Barotrauma into develop
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
#if SERVER
|
||||
using PipeType = System.IO.Pipes.AnonymousPipeClientStream;
|
||||
@@ -121,6 +121,7 @@ namespace Barotrauma.Networking
|
||||
readCancellationToken?.Cancel();
|
||||
return Option<int>.None();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (readTask.IsCompleted || readTask.Wait(timeOutMilliseconds, readCancellationToken.Token))
|
||||
@@ -327,7 +328,36 @@ namespace Barotrauma.Networking
|
||||
writeManualResetEvent.Set();
|
||||
}
|
||||
|
||||
public static bool Read(out byte[] msg)
|
||||
private static readonly Stopwatch stopwatch = new Stopwatch();
|
||||
private const int MaxMilliseconds = 8;
|
||||
|
||||
public static IEnumerable<byte[]> Read()
|
||||
{
|
||||
stopwatch.Restart();
|
||||
|
||||
// To avoid the stopwatch somehow experiencing magical overhead that makes it not even
|
||||
// start the loop within 8ms, use this bool to force at least one iteration.
|
||||
bool hasIteratedAtLeastOnce = false;
|
||||
|
||||
// If it's taken more than 8 milliseconds to read dequeued messages, take
|
||||
// a break from reading and allow all of the other logic to run for a tick.
|
||||
// Otherwise the server may overwhelm the host client with redundant messages
|
||||
// that are being read too slowly.
|
||||
while (!hasIteratedAtLeastOnce || stopwatch.ElapsedMilliseconds < MaxMilliseconds)
|
||||
{
|
||||
hasIteratedAtLeastOnce = true;
|
||||
if (!ReadSingleMessage(out var msg))
|
||||
{
|
||||
// No more messages available to dequeue, we don't need
|
||||
// to reach 8 milliseconds to know we're done here
|
||||
break;
|
||||
}
|
||||
yield return msg;
|
||||
}
|
||||
stopwatch.Stop();
|
||||
}
|
||||
|
||||
private static bool ReadSingleMessage(out byte[] msg)
|
||||
{
|
||||
if (HasShutDown) { msg = null; return false; }
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
PERMISSIONS, //tell the client which special permissions they have (if any)
|
||||
ACHIEVEMENT, //give the client a steam achievement
|
||||
ACHIEVEMENT_STAT, //increment stat for an achievement
|
||||
CHEATS_ENABLED, //tell the clients whether cheats are on or off
|
||||
|
||||
CAMPAIGN_SETUP_INFO,
|
||||
@@ -151,7 +152,7 @@ namespace Barotrauma.Networking
|
||||
ServerCrashed,
|
||||
ServerFull,
|
||||
AuthenticationRequired,
|
||||
SteamAuthenticationFailed,
|
||||
AuthenticationFailed,
|
||||
SessionTaken,
|
||||
TooManyFailedLogins,
|
||||
InvalidName,
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
#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);
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
#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);
|
||||
}
|
||||
}
|
||||
@@ -18,15 +18,16 @@ namespace Barotrauma.Networking
|
||||
/// 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 readonly ImmutableArray<AccountId> OtherMatchingIds;
|
||||
|
||||
public bool IsNone => AccountId.IsNone() && OtherMatchingIds.Length == 0;
|
||||
|
||||
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();
|
||||
OtherMatchingIds = otherIds.Where(id => !accountId.ValueEquals(id)).ToImmutableArray();
|
||||
}
|
||||
|
||||
public bool Matches(AccountId accountId)
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
#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);
|
||||
}
|
||||
}
|
||||
-79
@@ -1,79 +0,0 @@
|
||||
#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; }
|
||||
if (netAddress.IsIPv4MappedToIPv6) { netAddress = netAddress.MapToIPv4(); }
|
||||
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 static Option<LidgrenAddress> ParseHostName(string endpointStr)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
#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
@@ -1,37 +0,0 @@
|
||||
#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
@@ -1,16 +0,0 @@
|
||||
#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;
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Steam;
|
||||
|
||||
namespace Barotrauma.Networking;
|
||||
|
||||
public enum AuthenticationTicketKind
|
||||
{
|
||||
SteamAuthTicketForSteamHost = 0,
|
||||
SteamAuthTicketForEosHost = 1,
|
||||
EgsOwnershipToken = 2
|
||||
}
|
||||
|
||||
[NetworkSerialize(ArrayMaxSize = UInt16.MaxValue)]
|
||||
readonly record struct AuthenticationTicket(
|
||||
AuthenticationTicketKind Kind,
|
||||
ImmutableArray<byte> Data) : INetSerializableStruct
|
||||
{
|
||||
public static async Task<Option<AuthenticationTicket>> Create(Endpoint serverEndpoint)
|
||||
{
|
||||
if (SteamManager.IsInitialized && SteamManager.GetSteamId().TryUnwrap(out var steamId))
|
||||
{
|
||||
if (serverEndpoint is EosP2PEndpoint)
|
||||
{
|
||||
var authTicket = await SteamManager.GetAuthTicketForEosHostAuth();
|
||||
return authTicket
|
||||
.Bind(t => t.Data != null ? Option.Some(t.Data) : Option.None)
|
||||
.Select(data => new AuthenticationTicket(AuthenticationTicketKind.SteamAuthTicketForEosHost, data.ToImmutableArray()));
|
||||
}
|
||||
else
|
||||
{
|
||||
var authTicket = SteamManager.GetAuthSessionTicketForSteamHost(serverEndpoint);
|
||||
var steamIdBytes = BitConverter.GetBytes(steamId.Value);
|
||||
return authTicket
|
||||
.Bind(t => t.Data != null ? Option.Some(t.Data) : Option.None)
|
||||
.Select(data => new AuthenticationTicket(
|
||||
AuthenticationTicketKind.SteamAuthTicketForSteamHost,
|
||||
steamIdBytes.Concat(data).ToImmutableArray()));
|
||||
}
|
||||
}
|
||||
|
||||
if (EosInterface.IdQueries.GetLoggedInPuids() is { Length: > 0 } puids)
|
||||
{
|
||||
var externalAccountIdsResult = await EosInterface.IdQueries.GetSelfExternalAccountIds(puids[0]);
|
||||
if (externalAccountIdsResult.TryUnwrapSuccess(out var externalAccountIds))
|
||||
{
|
||||
var epicAccountIdOption = externalAccountIds.OfType<EpicAccountId>().FirstOrNone();
|
||||
if (epicAccountIdOption.TryUnwrap(out var epicAccountId))
|
||||
{
|
||||
return (await EosInterface.Ownership.GetGameOwnershipToken(epicAccountId))
|
||||
.Select(t => new AuthenticationTicket(
|
||||
AuthenticationTicketKind.EgsOwnershipToken,
|
||||
Encoding.UTF8.GetBytes(t.Jwt.ToString()).ToImmutableArray()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Option.None;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Threading.Tasks;
|
||||
using Barotrauma.Steam;
|
||||
|
||||
namespace Barotrauma.Networking;
|
||||
|
||||
abstract class Authenticator
|
||||
{
|
||||
public abstract Task<AccountInfo> VerifyTicket(AuthenticationTicket ticket);
|
||||
public abstract void EndAuthSession(AccountId accountId);
|
||||
|
||||
public static ImmutableDictionary<AuthenticationTicketKind, Authenticator> GetAuthenticatorsForHost(Option<Endpoint> ownerEndpointOption)
|
||||
{
|
||||
var authenticators = new Dictionary<AuthenticationTicketKind, Authenticator>();
|
||||
|
||||
if (EosInterface.Core.IsInitialized)
|
||||
{
|
||||
// Every kind of host should be able to do EOS ID Token authentication if they have EOS enabled
|
||||
authenticators.Add(AuthenticationTicketKind.EgsOwnershipToken, new EgsOwnershipTokenAuthenticator());
|
||||
|
||||
if (ownerEndpointOption.TryUnwrap(out var ownerEndpoint) && ownerEndpoint is EosP2PEndpoint)
|
||||
{
|
||||
// EOS P2P hosts do not have access to Steamworks
|
||||
authenticators.Add(AuthenticationTicketKind.SteamAuthTicketForEosHost, new SteamAuthTicketForEosHostAuthenticator());
|
||||
}
|
||||
}
|
||||
|
||||
if (!(ownerEndpointOption.TryUnwrap(out var ownerEndpoint2) && ownerEndpoint2 is EosP2PEndpoint) && SteamManager.IsInitialized)
|
||||
{
|
||||
// Steam P2P hosts and dedicated servers have access to Steamworks
|
||||
authenticators.Add(AuthenticationTicketKind.SteamAuthTicketForSteamHost, new SteamAuthTicketForSteamHostAuthenticator());
|
||||
}
|
||||
|
||||
return authenticators.ToImmutableDictionary();
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Barotrauma.Networking;
|
||||
|
||||
sealed class EgsOwnershipTokenAuthenticator : Authenticator
|
||||
{
|
||||
public override async Task<AccountInfo> VerifyTicket(AuthenticationTicket ticket)
|
||||
{
|
||||
var jwtOption = JsonWebToken.Parse(Encoding.UTF8.GetString(ticket.Data.AsSpan()));
|
||||
|
||||
if (!jwtOption.TryUnwrap(out var jwt)) { return AccountInfo.None; }
|
||||
var ownershipToken = new EosInterface.Ownership.Token(jwt);
|
||||
var accountIdOption = await ownershipToken.Verify();
|
||||
|
||||
if (!accountIdOption.TryUnwrap(out var accountId)) { return AccountInfo.None; }
|
||||
return new AccountInfo(accountId);
|
||||
}
|
||||
|
||||
public override void EndAuthSession(AccountId accountId) { /* do nothing */ }
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using RestSharp;
|
||||
|
||||
namespace Barotrauma.Networking;
|
||||
|
||||
sealed class SteamAuthTicketForEosHostAuthenticator : Authenticator
|
||||
{
|
||||
#warning FIXME change URL back to the non-experimental one once this passes QA
|
||||
private const string consentServerUrl = "https://barotraumagame.com/baromaster/experimental/";
|
||||
private const string consentServerFile = "getOwnerSteamId.php";
|
||||
private const int RemoteRequestVersion = 1;
|
||||
|
||||
public override async Task<AccountInfo> VerifyTicket(AuthenticationTicket ticket)
|
||||
{
|
||||
string ticketData = ToolBoxCore.ByteArrayToHexString(ticket.Data);
|
||||
|
||||
var client = new RestClient(consentServerUrl);
|
||||
|
||||
var request = new RestRequest(consentServerFile, Method.GET);
|
||||
request.AddParameter("authticket", ticketData);
|
||||
request.AddParameter("request_version", RemoteRequestVersion);
|
||||
|
||||
var response = await client.ExecuteAsync(request, Method.GET);
|
||||
if (!response.IsSuccessful) { return AccountInfo.None; }
|
||||
|
||||
try
|
||||
{
|
||||
var jsonDoc = JsonDocument.Parse(response.Content);
|
||||
Option<SteamId> steamId = Option.None;
|
||||
Option<SteamId> ownerSteamId = Option.None;
|
||||
foreach (var property in jsonDoc.RootElement.EnumerateObject())
|
||||
{
|
||||
if (!property.Name.ToIdentifier().Contains("SteamId")) { continue; }
|
||||
var accountIdOption = SteamId.Parse(property.Value.GetString() ?? "");
|
||||
if (accountIdOption.IsNone()) { continue; }
|
||||
if (property.Name.ToIdentifier() == "SteamId")
|
||||
{
|
||||
steamId = accountIdOption;
|
||||
}
|
||||
else if (property.Name.ToIdentifier() == "OwnerSteamId")
|
||||
{
|
||||
ownerSteamId = accountIdOption;
|
||||
}
|
||||
}
|
||||
var otherIds = ownerSteamId.TryUnwrap(out var id) ? new AccountId[] { id } : Array.Empty<AccountId>();
|
||||
return new AccountInfo(steamId.Select(static id => (AccountId)id), otherIds);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return AccountInfo.None;
|
||||
}
|
||||
}
|
||||
|
||||
public override void EndAuthSession(AccountId accountId) { /* do nothing */ }
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Barotrauma.Steam;
|
||||
|
||||
namespace Barotrauma.Networking;
|
||||
|
||||
#if CLIENT
|
||||
using SteamAuthSessionInterface = Steamworks.SteamUser;
|
||||
#else
|
||||
using SteamAuthSessionInterface = Steamworks.SteamServer;
|
||||
#endif
|
||||
|
||||
sealed class SteamAuthTicketForSteamHostAuthenticator : Authenticator
|
||||
{
|
||||
private static Steamworks.BeginAuthResult BeginAuthSession(Steamworks.AuthTicket authTicket, SteamId clientSteamId)
|
||||
{
|
||||
if (!SteamManager.IsInitialized) { return Steamworks.BeginAuthResult.ServerNotConnectedToSteam; }
|
||||
if (authTicket.Data is null) { return Steamworks.BeginAuthResult.InvalidTicket; }
|
||||
|
||||
DebugConsole.Log("Authenticating Steam client " + clientSteamId);
|
||||
Steamworks.BeginAuthResult startResult = SteamAuthSessionInterface.BeginAuthSession(authTicket.Data, clientSteamId.Value);
|
||||
if (startResult != Steamworks.BeginAuthResult.OK)
|
||||
{
|
||||
DebugConsole.Log($"Steam authentication failed: failed to start auth session ({startResult})");
|
||||
}
|
||||
|
||||
return startResult;
|
||||
}
|
||||
|
||||
private static void EndAuthSession(SteamId clientSteamId)
|
||||
{
|
||||
if (!SteamManager.IsInitialized) { return; }
|
||||
|
||||
DebugConsole.Log($"Ending auth session with Steam client {clientSteamId}");
|
||||
SteamAuthSessionInterface.EndAuthSession(clientSteamId.Value);
|
||||
}
|
||||
|
||||
public override async Task<AccountInfo> VerifyTicket(AuthenticationTicket ticket)
|
||||
{
|
||||
if (ticket.Data.Length < 8) { return AccountInfo.None; }
|
||||
|
||||
var ticketData = ticket.Data.ToArray();
|
||||
var steamAuthTicket = new Steamworks.AuthTicket { Data = ticketData[8..] };
|
||||
var steamId = new SteamId(BitConverter.ToUInt64(ticketData.AsSpan()[..8]));
|
||||
|
||||
using var janitor = Janitor.Start();
|
||||
|
||||
(Steamworks.AuthResponse AuthResponse, SteamId OwnerSteamId)? authResult = null;
|
||||
void onValidateAuthTicketResponse(Steamworks.SteamId clientId, Steamworks.SteamId ownerClientId, Steamworks.AuthResponse response)
|
||||
{
|
||||
if (clientId != steamId.Value) { response = Steamworks.AuthResponse.AuthTicketInvalid; }
|
||||
authResult = (response, new SteamId(ownerClientId));
|
||||
}
|
||||
|
||||
SteamAuthSessionInterface.OnValidateAuthTicketResponse += onValidateAuthTicketResponse;
|
||||
janitor.AddAction(() => SteamAuthSessionInterface.OnValidateAuthTicketResponse -= onValidateAuthTicketResponse);
|
||||
var beginAuthSessionResult = BeginAuthSession(steamAuthTicket, steamId);
|
||||
|
||||
if (beginAuthSessionResult != Steamworks.BeginAuthResult.OK) { return AccountInfo.None; }
|
||||
|
||||
while (authResult is null)
|
||||
{
|
||||
await Task.Delay(32);
|
||||
}
|
||||
if (authResult.Value.AuthResponse != Steamworks.AuthResponse.OK) { return AccountInfo.None; }
|
||||
|
||||
return new AccountInfo(steamId, authResult.Value.OwnerSteamId);
|
||||
}
|
||||
|
||||
public override void EndAuthSession(AccountId accountId)
|
||||
{
|
||||
if (accountId is not SteamId steamId) { return; }
|
||||
SteamAuthSessionInterface.EndAuthSession(steamId.Value);
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
#nullable enable
|
||||
|
||||
|
||||
namespace Barotrauma.Networking;
|
||||
|
||||
sealed class EosP2PEndpoint : P2PEndpoint
|
||||
{
|
||||
public EosInterface.ProductUserId ProductUserId => new EosInterface.ProductUserId((Address as EosP2PAddress)!.EosStringRepresentation);
|
||||
|
||||
public EosP2PEndpoint(EosInterface.ProductUserId puid) : this(new EosP2PAddress(puid.Value)) { }
|
||||
|
||||
public EosP2PEndpoint(EosP2PAddress address) : base(address) { }
|
||||
|
||||
public override string StringRepresentation => (Address as EosP2PAddress)!.StringRepresentation;
|
||||
|
||||
public override LocalizedString ServerTypeString { get; } = TextManager.Get("PlayerHostedServer");
|
||||
|
||||
public override int GetHashCode()
|
||||
=> (Address as EosP2PAddress)!.GetHashCode();
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is EosP2PEndpoint otherEndpoint
|
||||
&& ProductUserId == otherEndpoint.ProductUserId;
|
||||
|
||||
public new static Option<EosP2PEndpoint> Parse(string endpointStr)
|
||||
=> EosP2PAddress.Parse(endpointStr).Select(eosAddress => new EosP2PEndpoint(eosAddress));
|
||||
|
||||
public const string SocketName = "Barotrauma.EosP2PSocket";
|
||||
|
||||
public override P2PConnection MakeConnectionFromEndpoint()
|
||||
=> new EosP2PConnection(this);
|
||||
}
|
||||
+1
-1
@@ -60,7 +60,7 @@ namespace Barotrauma.Networking
|
||||
=> NetEndpoint.GetHashCode();
|
||||
|
||||
public static bool operator ==(LidgrenEndpoint a, LidgrenEndpoint b)
|
||||
=> a.Address.Equals(b.Address) && a.Port == b.Port;
|
||||
=> a.NetEndpoint.EquivalentTo(b.NetEndpoint);
|
||||
|
||||
public static bool operator !=(LidgrenEndpoint a, LidgrenEndpoint b)
|
||||
=> !(a == b);
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
#nullable enable
|
||||
namespace Barotrauma.Networking;
|
||||
|
||||
abstract class P2PEndpoint : Endpoint
|
||||
{
|
||||
protected P2PEndpoint(P2PAddress address) : base(address) { }
|
||||
|
||||
public abstract P2PConnection MakeConnectionFromEndpoint();
|
||||
|
||||
public new static Option<P2PEndpoint> Parse(string str)
|
||||
=> Endpoint.Parse(str).Bind(ep => ep is P2PEndpoint pep ? Option.Some(pep) : Option.None);
|
||||
}
|
||||
+12
-21
@@ -2,36 +2,27 @@
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
sealed class SteamP2PEndpoint : Endpoint
|
||||
sealed class SteamP2PEndpoint : P2PEndpoint
|
||||
{
|
||||
public readonly SteamId SteamId;
|
||||
public SteamId SteamId => (Address as SteamP2PAddress)!.SteamId;
|
||||
|
||||
public override string StringRepresentation => SteamId.StringRepresentation;
|
||||
|
||||
public override LocalizedString ServerTypeString { get; } = TextManager.Get("SteamP2PServer");
|
||||
public override LocalizedString ServerTypeString { get; } = TextManager.Get("PlayerHostedServer");
|
||||
|
||||
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 SteamP2PEndpoint(SteamId steamId) : base(new SteamP2PAddress(steamId)) { }
|
||||
|
||||
public override int GetHashCode()
|
||||
=> SteamId.GetHashCode();
|
||||
|
||||
public static bool operator ==(SteamP2PEndpoint a, SteamP2PEndpoint b)
|
||||
=> a.SteamId == b.SteamId;
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is SteamP2PEndpoint otherEndpoint
|
||||
&& this.SteamId == otherEndpoint.SteamId;
|
||||
|
||||
public static bool operator !=(SteamP2PEndpoint a, SteamP2PEndpoint b)
|
||||
=> !(a == b);
|
||||
public new static Option<SteamP2PEndpoint> Parse(string endpointStr)
|
||||
=> SteamId.Parse(endpointStr).Select(steamId => new SteamP2PEndpoint(steamId));
|
||||
|
||||
public override P2PConnection MakeConnectionFromEndpoint()
|
||||
=> new SteamP2PConnection(this);
|
||||
}
|
||||
}
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Networking;
|
||||
|
||||
sealed class MessageDefragmenter
|
||||
{
|
||||
private readonly Dictionary<ushort, MessageFragment[]> partialMessages = new Dictionary<ushort, MessageFragment[]>();
|
||||
|
||||
public Option<ImmutableArray<byte>> ProcessIncomingFragment(MessageFragment fragment)
|
||||
{
|
||||
if (!partialMessages.ContainsKey(fragment.FragmentId.MessageId))
|
||||
{
|
||||
partialMessages[fragment.FragmentId.MessageId] = new MessageFragment[fragment.FragmentId.FragmentCount];
|
||||
}
|
||||
else if (partialMessages[fragment.FragmentId.MessageId].Length != fragment.FragmentId.FragmentCount)
|
||||
{
|
||||
DebugConsole.AddWarning($"Got a fragment for message {fragment.FragmentId.MessageId} " +
|
||||
$"with a mismatched expected fragment count");
|
||||
return Option.None;
|
||||
}
|
||||
|
||||
var fragmentBuffer = partialMessages[fragment.FragmentId.MessageId];
|
||||
if (fragment.FragmentId.FragmentIndex >= fragmentBuffer.Length)
|
||||
{
|
||||
DebugConsole.AddWarning($"Got a fragment for message {fragment.FragmentId.MessageId} " +
|
||||
$"with an index greater than or equal to the expected fragment count ({fragment.FragmentId.FragmentIndex} >= {fragmentBuffer.Length})");
|
||||
return Option.None;
|
||||
}
|
||||
|
||||
fragmentBuffer[fragment.FragmentId.FragmentIndex] = fragment;
|
||||
if (fragmentBuffer.All(f => !f.Data.IsDefault && f.FragmentId.MessageId == fragment.FragmentId.MessageId))
|
||||
{
|
||||
partialMessages.Remove(fragment.FragmentId.MessageId);
|
||||
return Option.Some(fragmentBuffer.SelectMany(f => f.Data).ToImmutableArray());
|
||||
}
|
||||
return Option.None;
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Barotrauma.Networking;
|
||||
|
||||
[NetworkSerialize(ArrayMaxSize = MaxSize)]
|
||||
readonly record struct MessageFragment(
|
||||
MessageFragment.Id FragmentId,
|
||||
ImmutableArray<byte> Data) : INetSerializableStruct
|
||||
{
|
||||
public const int MaxSize = 1100;
|
||||
|
||||
[NetworkSerialize]
|
||||
public readonly record struct Id(
|
||||
ushort FragmentIndex,
|
||||
ushort FragmentCount,
|
||||
ushort MessageId) : INetSerializableStruct;
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Barotrauma.Networking;
|
||||
|
||||
sealed class MessageFragmenter
|
||||
{
|
||||
private UInt16 nextFragmentedMessageId = 0;
|
||||
private readonly List<MessageFragment> fragments = new List<MessageFragment>();
|
||||
|
||||
public ImmutableArray<MessageFragment> FragmentMessage(ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
UInt16 msgId = nextFragmentedMessageId;
|
||||
nextFragmentedMessageId++;
|
||||
|
||||
int roundedByteCount = bytes.Length;
|
||||
roundedByteCount += (MessageFragment.MaxSize - (roundedByteCount % MessageFragment.MaxSize)) % MessageFragment.MaxSize;
|
||||
|
||||
int fragmentCount = roundedByteCount / MessageFragment.MaxSize;
|
||||
fragments.Clear();
|
||||
fragments.EnsureCapacity(fragmentCount);
|
||||
for (int i = 0; i < fragmentCount; i++)
|
||||
{
|
||||
var subset = bytes[(i * MessageFragment.MaxSize)..];
|
||||
if (subset.Length > MessageFragment.MaxSize) { subset = subset[..MessageFragment.MaxSize]; }
|
||||
|
||||
fragments.Add(new MessageFragment(
|
||||
FragmentId: new MessageFragment.Id(
|
||||
FragmentIndex: (ushort)i,
|
||||
FragmentCount: (ushort)fragmentCount,
|
||||
MessageId: msgId),
|
||||
Data: subset.ToArray().ToImmutableArray()));
|
||||
}
|
||||
|
||||
return fragments.ToImmutableArray();
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,11 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
public static class MsgConstants
|
||||
{
|
||||
public const int MTU = 1200; //TODO: determine dynamically
|
||||
// MTU currently set to the upper limit of what EOS P2P can do
|
||||
// TODO: determine dynamically so other protocols can use a larger MTU,
|
||||
// as well as handle a client with a lower MTU set outside of our control
|
||||
public const int MTU = 1170;
|
||||
|
||||
public const int CompressionThreshold = 1000;
|
||||
public const int InitialBufferSize = 256;
|
||||
public const int BufferOverAllocateAmount = 4;
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Barotrauma.Networking;
|
||||
|
||||
sealed class EosP2PConnection : P2PConnection<EosP2PEndpoint>
|
||||
{
|
||||
public EosP2PConnection(EosP2PEndpoint endpoint) : base(endpoint) { }
|
||||
}
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
sealed class LidgrenConnection : NetworkConnection
|
||||
sealed class LidgrenConnection : NetworkConnection<LidgrenEndpoint>
|
||||
{
|
||||
public readonly NetConnection NetConnection;
|
||||
|
||||
|
||||
+10
-3
@@ -8,6 +8,13 @@ namespace Barotrauma.Networking
|
||||
Disconnected = 0x2
|
||||
}
|
||||
|
||||
abstract class NetworkConnection<T> : NetworkConnection where T : Endpoint
|
||||
{
|
||||
protected NetworkConnection(T endpoint) : base(endpoint) { }
|
||||
|
||||
public new T Endpoint => (base.Endpoint as T)!;
|
||||
}
|
||||
|
||||
abstract class NetworkConnection
|
||||
{
|
||||
public const double TimeoutThreshold = 60.0; //full minute for timeout because loading screens can take quite a while
|
||||
@@ -23,7 +30,7 @@ namespace Barotrauma.Networking
|
||||
get; set;
|
||||
}
|
||||
|
||||
public NetworkConnection(Endpoint endpoint)
|
||||
protected NetworkConnection(Endpoint endpoint)
|
||||
{
|
||||
Endpoint = endpoint;
|
||||
}
|
||||
@@ -35,9 +42,9 @@ namespace Barotrauma.Networking
|
||||
|
||||
public void SetAccountInfo(AccountInfo newInfo)
|
||||
{
|
||||
AccountInfo = newInfo;
|
||||
if (AccountInfo.IsNone) { AccountInfo = newInfo; }
|
||||
}
|
||||
|
||||
|
||||
public sealed override string ToString()
|
||||
=> Endpoint.StringRepresentation;
|
||||
}
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Barotrauma.Networking;
|
||||
|
||||
abstract class P2PConnection<T> : P2PConnection where T : P2PEndpoint
|
||||
{
|
||||
protected P2PConnection(T endpoint) : base(endpoint) { }
|
||||
|
||||
public new T Endpoint => (base.Endpoint as T)!;
|
||||
}
|
||||
|
||||
abstract class P2PConnection : NetworkConnection<P2PEndpoint>
|
||||
{
|
||||
protected P2PConnection(P2PEndpoint endpoint) : base(endpoint)
|
||||
{
|
||||
Heartbeat();
|
||||
}
|
||||
|
||||
public double Timeout = 0.0;
|
||||
|
||||
public void Decay(float deltaTime)
|
||||
{
|
||||
Timeout -= deltaTime;
|
||||
}
|
||||
|
||||
public void Heartbeat()
|
||||
{
|
||||
Timeout = TimeoutThreshold;
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -23,11 +23,11 @@ namespace Barotrauma.Networking
|
||||
=> !(a == b);
|
||||
}
|
||||
|
||||
sealed class PipeConnection : NetworkConnection
|
||||
sealed class PipeConnection : NetworkConnection<PipeEndpoint>
|
||||
{
|
||||
public PipeConnection(AccountId accountId) : base(new PipeEndpoint())
|
||||
public PipeConnection(Option<AccountId> accountId) : base(new PipeEndpoint())
|
||||
{
|
||||
SetAccountInfo(new AccountInfo(Option<AccountId>.Some(accountId)));
|
||||
SetAccountInfo(new AccountInfo(accountId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-17
@@ -1,24 +1,9 @@
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
sealed class SteamP2PConnection : NetworkConnection
|
||||
sealed class SteamP2PConnection : P2PConnection<SteamP2PEndpoint>
|
||||
{
|
||||
public double Timeout = 0.0;
|
||||
|
||||
public SteamP2PConnection(SteamId steamId) : this(new SteamP2PEndpoint(steamId)) { }
|
||||
|
||||
public SteamP2PConnection(SteamP2PEndpoint endpoint) : base(endpoint)
|
||||
{
|
||||
Heartbeat();
|
||||
}
|
||||
|
||||
public void Decay(float deltaTime)
|
||||
{
|
||||
Timeout -= deltaTime;
|
||||
}
|
||||
|
||||
public void Heartbeat()
|
||||
{
|
||||
Timeout = TimeoutThreshold;
|
||||
}
|
||||
public SteamP2PConnection(SteamP2PEndpoint endpoint) : base(endpoint) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
public enum DeliveryMethod : int
|
||||
{
|
||||
Unreliable = 0x0,
|
||||
Reliable = 0x1,
|
||||
ReliableOrdered = 0x2
|
||||
}
|
||||
|
||||
public enum ConnectionInitialization : int
|
||||
{
|
||||
//used by all peer implementations
|
||||
SteamTicketAndVersion = 0x1,
|
||||
ContentPackageOrder = 0x2,
|
||||
Password = 0x3,
|
||||
Success = 0x0,
|
||||
|
||||
//used only by SteamP2P implementations
|
||||
ConnectionStarted = 0x4
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum PacketHeader : int
|
||||
{
|
||||
//used by all peer implementations
|
||||
None = 0x0,
|
||||
IsCompressed = 0x1,
|
||||
IsConnectionInitializationStep = 0x2,
|
||||
|
||||
//used only by SteamP2P implementations
|
||||
IsDisconnectMessage = 0x4,
|
||||
IsServerMessage = 0x8,
|
||||
IsHeartbeatMessage = 0x10
|
||||
}
|
||||
|
||||
public static class NetworkEnumExtensions
|
||||
{
|
||||
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);
|
||||
|
||||
public static bool IsDisconnectMessage(this PacketHeader h)
|
||||
=> h.HasFlag(PacketHeader.IsDisconnectMessage);
|
||||
|
||||
public static bool IsServerMessage(this PacketHeader h)
|
||||
=> h.HasFlag(PacketHeader.IsServerMessage);
|
||||
|
||||
public static bool IsHeartbeatMessage(this PacketHeader h)
|
||||
=> h.HasFlag(PacketHeader.IsHeartbeatMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,8 +52,7 @@ namespace Barotrauma.Networking
|
||||
deliveryMethod switch
|
||||
{
|
||||
DeliveryMethod.Unreliable => NetDeliveryMethod.Unreliable,
|
||||
DeliveryMethod.Reliable => NetDeliveryMethod.ReliableUnordered,
|
||||
DeliveryMethod.ReliableOrdered => NetDeliveryMethod.ReliableOrdered,
|
||||
DeliveryMethod.Reliable => NetDeliveryMethod.ReliableOrdered,
|
||||
_ => NetDeliveryMethod.Unreliable
|
||||
};
|
||||
|
||||
@@ -61,7 +60,6 @@ namespace Barotrauma.Networking
|
||||
deliveryMethod switch
|
||||
{
|
||||
DeliveryMethod.Reliable => Steamworks.P2PSend.Reliable,
|
||||
DeliveryMethod.ReliableOrdered => Steamworks.P2PSend.Reliable,
|
||||
_ => Steamworks.P2PSend.Unreliable
|
||||
};
|
||||
}
|
||||
|
||||
+38
-32
@@ -25,34 +25,42 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
[NetworkSerialize(ArrayMaxSize = ushort.MaxValue)]
|
||||
internal struct ClientSteamTicketAndVersionPacket : INetSerializableStruct
|
||||
internal struct ClientAuthTicketAndVersionPacket : 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 Option<AccountId> AccountId;
|
||||
public Option<AuthenticationTicket> AuthTicket;
|
||||
public string GameVersion;
|
||||
public Identifier Language;
|
||||
}
|
||||
|
||||
[NetworkSerialize]
|
||||
internal struct SteamP2PInitializationRelayPacket : INetSerializableStruct
|
||||
internal readonly record struct P2POwnerToServerHeader
|
||||
(string? EndpointStr, AccountInfo AccountInfo) : INetSerializableStruct
|
||||
{
|
||||
public Option<P2PEndpoint> Endpoint => P2PEndpoint.Parse(EndpointStr ?? "");
|
||||
}
|
||||
|
||||
[NetworkSerialize]
|
||||
internal readonly record struct P2PServerToOwnerHeader
|
||||
(string? EndpointStr) : INetSerializableStruct
|
||||
{
|
||||
public Option<P2PEndpoint> Endpoint => P2PEndpoint.Parse(EndpointStr ?? "");
|
||||
}
|
||||
|
||||
[NetworkSerialize]
|
||||
internal struct P2PInitializationRelayPacket : INetSerializableStruct
|
||||
{
|
||||
public ulong LobbyID;
|
||||
public PeerPacketMessage Message;
|
||||
}
|
||||
|
||||
[NetworkSerialize]
|
||||
internal struct SteamP2PInitializationOwnerPacket : INetSerializableStruct
|
||||
{
|
||||
public string OwnerName;
|
||||
}
|
||||
internal readonly record struct P2PInitializationOwnerPacket(
|
||||
string Name,
|
||||
AccountId AccountId)
|
||||
: INetSerializableStruct;
|
||||
|
||||
|
||||
[NetworkSerialize(ArrayMaxSize = ushort.MaxValue)]
|
||||
@@ -68,7 +76,7 @@ namespace Barotrauma.Networking
|
||||
public byte[] Buffer;
|
||||
public readonly int Length => Buffer.Length;
|
||||
|
||||
public readonly IReadMessage GetReadMessageUncompressed() => new ReadWriteMessage(Buffer, 0, Length, copyBuf: false);
|
||||
public readonly IReadMessage GetReadMessageUncompressed() => new ReadWriteMessage(Buffer, 0, Length * 8, copyBuf: false);
|
||||
public readonly IReadMessage GetReadMessage(bool isCompressed, NetworkConnection conn) => new ReadOnlyMessage(Buffer, isCompressed, 0, Length, conn);
|
||||
}
|
||||
|
||||
@@ -140,7 +148,8 @@ namespace Barotrauma.Networking
|
||||
DisconnectReason.ExcessiveDesyncOldEvent => ServerMessage,
|
||||
DisconnectReason.ExcessiveDesyncRemovedEvent => ServerMessage,
|
||||
DisconnectReason.SyncTimeout => ServerMessage,
|
||||
_ => TextManager.Get($"DisconnectReason.{DisconnectReason}").Fallback(TextManager.Get("ConnectionLost"))
|
||||
DisconnectReason.AuthenticationFailed => TextManager.Get($"DisconnectReason.{DisconnectReason}").Fallback(TextManager.Get("ChatMsg.DisconnectReason.AuthenticationRequired")),
|
||||
_ => TextManager.Get($"DisconnectReason.{DisconnectReason}").Fallback($"{TextManager.Get("ConnectionLost")} ({DisconnectReason})")
|
||||
};
|
||||
|
||||
public LocalizedString ReconnectMessage
|
||||
@@ -178,15 +187,12 @@ namespace Barotrauma.Networking
|
||||
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.
|
||||
/// This exists because Lidgren 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()
|
||||
{
|
||||
@@ -194,7 +200,7 @@ namespace Barotrauma.Networking
|
||||
=> Convert.ToBase64String(Encoding.UTF8.GetBytes(str));
|
||||
|
||||
return DisconnectReason
|
||||
+ lidgrenSeparator
|
||||
+ NetworkMagicStrings.LidgrenDisconnectSeparator
|
||||
+ strToBase64(AdditionalInformation);
|
||||
}
|
||||
|
||||
@@ -209,16 +215,16 @@ namespace Barotrauma.Networking
|
||||
case Lidgren.Network.NetConnection.NoResponseMessage:
|
||||
case "Connection timed out":
|
||||
case "Reconnecting":
|
||||
return Option<PeerDisconnectPacket>.Some(WithReason(DisconnectReason.Timeout));
|
||||
return Option.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])));
|
||||
string[] split = str.Split(NetworkMagicStrings.LidgrenDisconnectSeparator);
|
||||
if (split.Length != 2) { return Option.None; }
|
||||
if (!Enum.TryParse(split[0], out DisconnectReason disconnectReason)) { return Option.None; }
|
||||
return Option.Some(new PeerDisconnectPacket(disconnectReason, base64ToStr(split[1])));
|
||||
}
|
||||
|
||||
public static PeerDisconnectPacket Custom(string customMessage)
|
||||
@@ -247,12 +253,12 @@ namespace Barotrauma.Networking
|
||||
|
||||
public static PeerDisconnectPacket SteamAuthError(Steamworks.BeginAuthResult error)
|
||||
=> new PeerDisconnectPacket(
|
||||
DisconnectReason.SteamAuthenticationFailed,
|
||||
DisconnectReason.AuthenticationFailed,
|
||||
$"{nameof(Steamworks.BeginAuthResult)}.{error}");
|
||||
|
||||
public static PeerDisconnectPacket SteamAuthError(Steamworks.AuthResponse error)
|
||||
=> new PeerDisconnectPacket(
|
||||
DisconnectReason.SteamAuthenticationFailed,
|
||||
DisconnectReason.AuthenticationFailed,
|
||||
$"{nameof(Steamworks.AuthResponse)}.{error}");
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace Barotrauma.Networking;
|
||||
|
||||
public readonly record struct ServerListContentPackageInfo(
|
||||
string Name, string Hash, Option<ContentPackageId> Id)
|
||||
{
|
||||
public ServerListContentPackageInfo(ContentPackage pkg)
|
||||
: this(pkg.Name, pkg.Hash.StringRepresentation, pkg.UgcId) {}
|
||||
|
||||
public static Option<ServerListContentPackageInfo> ParseSingleEntry(string singleEntry)
|
||||
{
|
||||
if (singleEntry.SplitEscaped(',') is not { Count: 3 } split) { return Option.None; }
|
||||
|
||||
return Option.Some(
|
||||
new ServerListContentPackageInfo(
|
||||
split[0],
|
||||
split[1],
|
||||
ContentPackageId.Parse(split[2])));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> new[] { Name, Hash, Id.Select(id => id.StringRepresentation).Fallback("") }
|
||||
.JoinEscaped(',');
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
@@ -296,7 +297,7 @@ namespace Barotrauma.Networking
|
||||
if (typeName != null || property.PropertyType.IsEnum)
|
||||
{
|
||||
NetPropertyData netPropertyData = new NetPropertyData(this, property, typeName);
|
||||
UInt32 key = ToolBox.IdentifierToUint32Hash(netPropertyData.Name, md5);
|
||||
UInt32 key = ToolBoxCore.IdentifierToUint32Hash(netPropertyData.Name, md5);
|
||||
if (key == 0) { key++; } //0 is reserved to indicate the end of the netproperties section of a message
|
||||
if (netProperties.ContainsKey(key)){ throw new Exception("Hashing collision in ServerSettings.netProperties: " + netProperties[key] + " has same key as " + property.Name + " (" + key.ToString() + ")"); }
|
||||
netProperties.Add(key, netPropertyData);
|
||||
@@ -313,7 +314,7 @@ namespace Barotrauma.Networking
|
||||
if (typeName != null || property.PropertyType.IsEnum)
|
||||
{
|
||||
NetPropertyData netPropertyData = new NetPropertyData(networkMember.KarmaManager, property, typeName);
|
||||
UInt32 key = ToolBox.IdentifierToUint32Hash(netPropertyData.Name, md5);
|
||||
UInt32 key = ToolBoxCore.IdentifierToUint32Hash(netPropertyData.Name, md5);
|
||||
if (netProperties.ContainsKey(key)) { throw new Exception("Hashing collision in ServerSettings.netProperties: " + netProperties[key] + " has same key as " + property.Name + " (" + key.ToString() + ")"); }
|
||||
netProperties.Add(key, netPropertyData);
|
||||
}
|
||||
@@ -1140,5 +1141,47 @@ namespace Barotrauma.Networking
|
||||
msg.WriteUInt16((UInt16)subList.FindIndex(s => s.Name.Equals(submarineName, StringComparison.OrdinalIgnoreCase)));
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateServerListInfo(Action<Identifier, object> setter)
|
||||
{
|
||||
void set(string key, object obj) => setter(key.ToIdentifier(), obj);
|
||||
|
||||
set("ServerName", ServerName);
|
||||
set("MaxPlayers", MaxPlayers);
|
||||
set("HasPassword", HasPassword);
|
||||
set("message", ServerMessageText);
|
||||
set("version", GameMain.Version);
|
||||
set("playercount", GameMain.NetworkMember.ConnectedClients.Count);
|
||||
set("contentpackages", ContentPackageManager.EnabledPackages.All.Where(p => p.HasMultiplayerSyncedContent));
|
||||
set("modeselectionmode", ModeSelectionMode);
|
||||
set("subselectionmode", SubSelectionMode);
|
||||
set("voicechatenabled", VoiceChatEnabled);
|
||||
set("allowspectating", AllowSpectating);
|
||||
set("allowrespawn", AllowRespawn);
|
||||
set("traitors", TraitorProbability.ToString(CultureInfo.InvariantCulture));
|
||||
set("friendlyfireenabled", AllowFriendlyFire);
|
||||
set("karmaenabled", KarmaEnabled);
|
||||
set("gamestarted", GameMain.NetworkMember.GameStarted);
|
||||
set("gamemode", GameModeIdentifier);
|
||||
set("playstyle", PlayStyle);
|
||||
set("language", Language.ToString());
|
||||
#if SERVER
|
||||
set("eoscrossplay", EosInterface.Core.IsInitialized);
|
||||
#else
|
||||
set("eoscrossplay", EosInterface.IdQueries.IsLoggedIntoEosConnect || Eos.EosSessionManager.CurrentOwnedSession.IsSome());
|
||||
#endif
|
||||
if (GameMain.NetLobbyScreen?.SelectedSub != null)
|
||||
{
|
||||
set("submarine", GameMain.NetLobbyScreen.SelectedSub.Name);
|
||||
}
|
||||
if (Steamworks.SteamClient.IsLoggedOn)
|
||||
{
|
||||
string pingLocation = Steamworks.SteamNetworkingUtils.LocalPingLocation?.ToString();
|
||||
if (!pingLocation.IsNullOrEmpty())
|
||||
{
|
||||
set("steampinglocation", pingLocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user