v1.3.0.1 (Epic Store release)

This commit is contained in:
Regalis11
2024-03-28 18:34:33 +02:00
parent 81ca8637be
commit 3791670c42
269 changed files with 13160 additions and 2966 deletions
@@ -0,0 +1,82 @@
#nullable enable
namespace Barotrauma.Networking;
sealed class DualStackP2PSocket : P2PSocket
{
private readonly Option<EosP2PSocket> eosSocket;
private readonly Option<SteamListenSocket> steamSocket;
private DualStackP2PSocket(
Callbacks callbacks,
Option<EosP2PSocket> eosSocket,
Option<SteamListenSocket> steamSocket) :
base(callbacks)
{
this.eosSocket = eosSocket;
this.steamSocket = steamSocket;
}
public static Result<P2PSocket, Error> Create(Callbacks callbacks)
{
var eosP2PSocketResult = EosP2PSocket.Create(callbacks);
var steamP2PSocketResult = SteamListenSocket.Create(callbacks);
if (eosP2PSocketResult.TryUnwrapFailure(out var eosError)
&& steamP2PSocketResult.TryUnwrapFailure(out var steamError))
{
return Result.Failure(new Error(eosError, steamError));
}
return Result.Success((P2PSocket)new DualStackP2PSocket(
callbacks,
eosP2PSocketResult.TryUnwrapSuccess(out var eosP2PSocket)
? Option.Some((EosP2PSocket)eosP2PSocket)
: Option.None,
steamP2PSocketResult.TryUnwrapSuccess(out var steamP2PSocket)
? Option.Some((SteamListenSocket)steamP2PSocket)
: Option.None));
}
public override void ProcessIncomingMessages()
{
if (eosSocket.TryUnwrap(out var eosP2PSocket)) { eosP2PSocket.ProcessIncomingMessages(); }
if (steamSocket.TryUnwrap(out var steamP2PSocket)) { steamP2PSocket.ProcessIncomingMessages(); }
}
public override bool SendMessage(P2PEndpoint endpoint, IWriteMessage outMsg, DeliveryMethod deliveryMethod)
{
return endpoint switch
{
EosP2PEndpoint eosP2PEndpoint when eosSocket.TryUnwrap(out var eosP2PSocket)
=> eosP2PSocket.SendMessage(eosP2PEndpoint, outMsg, deliveryMethod),
SteamP2PEndpoint steamP2PEndpoint when steamSocket.TryUnwrap(out var steamP2PSocket)
=> steamP2PSocket.SendMessage(steamP2PEndpoint, outMsg, deliveryMethod),
_
=> false
};
}
public override void CloseConnection(P2PEndpoint endpoint)
{
switch (endpoint)
{
case EosP2PEndpoint eosP2PEndpoint:
if (eosSocket.TryUnwrap(out var eosP2PSocket))
{
eosP2PSocket.CloseConnection(eosP2PEndpoint);
}
break;
case SteamP2PEndpoint steamP2PEndpoint:
if (steamSocket.TryUnwrap(out var steamP2PSocket))
{
steamP2PSocket.CloseConnection(steamP2PEndpoint);
}
break;
}
}
public override void Dispose()
{
if (eosSocket.TryUnwrap(out var eosP2PSocket)) { eosP2PSocket.Dispose(); }
if (steamSocket.TryUnwrap(out var steamP2PSocket)) { steamP2PSocket.Dispose(); }
}
}
@@ -0,0 +1,99 @@
#nullable enable
namespace Barotrauma.Networking;
sealed class EosP2PSocket : P2PSocket
{
private readonly EosInterface.P2PSocket eosSocket;
private EosP2PSocket(
Callbacks callbacks,
EosInterface.P2PSocket eosSocket)
: base(callbacks)
{
this.eosSocket = eosSocket;
}
public static Result<P2PSocket, Error> Create(Callbacks callbacks)
{
if (!EosInterface.Core.IsInitialized) { return Result.Failure(new Error(ErrorCode.EosNotInitialized)); }
var eosSocketId = new EosInterface.SocketId { SocketName = EosP2PEndpoint.SocketName };
if (EosInterface.IdQueries.GetLoggedInPuids() is not { Length: > 0 } puids)
{
return Result.Failure(new Error(ErrorCode.EosNotLoggedIn));
}
var socketCreateResult = EosInterface.P2PSocket.Create(puids[0], eosSocketId);
if (!socketCreateResult.TryUnwrapSuccess(out var eosSocket)) { return Result.Failure(new Error(ErrorCode.FailedToCreateEosP2PSocket, socketCreateResult.ToString())); }
var retVal = new EosP2PSocket(callbacks, eosSocket);
eosSocket.HandleIncomingConnection.Register("Event".ToIdentifier(), retVal.OnIncomingConnection);
eosSocket.HandleClosedConnection.Register("Event".ToIdentifier(), retVal.OnConnectionClosed);
return Result.Success((P2PSocket)retVal);
}
public override void ProcessIncomingMessages()
{
foreach (var msg in eosSocket.GetMessageBatch())
{
callbacks.OnData(new EosP2PEndpoint(msg.Sender), new ReadWriteMessage(msg.Buffer, 0, msg.ByteLength * 8, false));
}
}
public override bool SendMessage(P2PEndpoint endpoint, IWriteMessage outMsg, DeliveryMethod deliveryMethod)
{
if (endpoint is not EosP2PEndpoint { ProductUserId: var puid }) { return false; }
var sendResult = eosSocket.SendMessage(new EosInterface.P2PSocket.OutgoingMessage(
Buffer: outMsg.Buffer,
ByteLength: outMsg.LengthBytes,
Destination: puid,
DeliveryMethod: deliveryMethod));
return sendResult.IsSuccess;
}
private void OnIncomingConnection(EosInterface.P2PSocket.IncomingConnectionRequest request)
{
var remoteEndpoint = new EosP2PEndpoint(request.RemoteUserId);
if (callbacks.OnIncomingConnection(remoteEndpoint))
{
request.Accept();
}
}
private void OnConnectionClosed(EosInterface.P2PSocket.RemoteConnectionClosed data)
{
var remoteEndpoint = new EosP2PEndpoint(data.RemoteUserId);
var peerDisconnectPacket = PeerDisconnectPacket.WithReason(data.Reason switch
{
EosInterface.P2PSocket.RemoteConnectionClosed.ConnectionClosedReason.Unknown => DisconnectReason.Unknown,
EosInterface.P2PSocket.RemoteConnectionClosed.ConnectionClosedReason.ClosedByLocalUser => DisconnectReason.Disconnected,
EosInterface.P2PSocket.RemoteConnectionClosed.ConnectionClosedReason.ClosedByPeer => DisconnectReason.Disconnected,
EosInterface.P2PSocket.RemoteConnectionClosed.ConnectionClosedReason.TimedOut => DisconnectReason.Timeout,
EosInterface.P2PSocket.RemoteConnectionClosed.ConnectionClosedReason.TooManyConnections => DisconnectReason.ServerFull,
EosInterface.P2PSocket.RemoteConnectionClosed.ConnectionClosedReason.InvalidMessage => DisconnectReason.Unknown,
EosInterface.P2PSocket.RemoteConnectionClosed.ConnectionClosedReason.InvalidData => DisconnectReason.Unknown,
EosInterface.P2PSocket.RemoteConnectionClosed.ConnectionClosedReason.ConnectionFailed => DisconnectReason.AuthenticationFailed,
EosInterface.P2PSocket.RemoteConnectionClosed.ConnectionClosedReason.ConnectionClosed => DisconnectReason.Disconnected,
EosInterface.P2PSocket.RemoteConnectionClosed.ConnectionClosedReason.NegotiationFailed => DisconnectReason.AuthenticationFailed,
EosInterface.P2PSocket.RemoteConnectionClosed.ConnectionClosedReason.UnexpectedError => DisconnectReason.Unknown,
EosInterface.P2PSocket.RemoteConnectionClosed.ConnectionClosedReason.Unhandled => DisconnectReason.Unknown,
_ => DisconnectReason.Unknown
});
callbacks.OnConnectionClosed(remoteEndpoint, peerDisconnectPacket);
}
public override void CloseConnection(P2PEndpoint endpoint)
{
if (endpoint is not EosP2PEndpoint { ProductUserId: var puid }) { return; }
eosSocket.CloseConnection(puid);
}
public override void Dispose()
{
eosSocket.Dispose();
}
}
@@ -0,0 +1,56 @@
#nullable enable
using System;
using System.Collections.Immutable;
using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma.Networking;
abstract class P2PSocket : IDisposable
{
public enum ErrorCode
{
EosNotInitialized,
EosNotLoggedIn,
FailedToCreateEosP2PSocket,
SteamNotInitialized,
FailedToCreateSteamP2PSocket
}
public readonly record struct Error(
ImmutableArray<(ErrorCode Code, string AdditionalInfo)> CodesAndInfo)
{
public Error(ErrorCode code, string? additionalInfo = "") : this((code, additionalInfo ?? "").ToEnumerable().ToImmutableArray()) { }
public Error(params Error[] innerErrors) : this(innerErrors.SelectMany(ie => ie.CodesAndInfo).ToImmutableArray()) { }
public override string? ToString()
{
if (CodesAndInfo.IsDefault)
{
return "default(Error)";
}
return $"Errors({string.Join("; ", CodesAndInfo)})";
}
}
public readonly record struct Callbacks(
Predicate<P2PEndpoint> OnIncomingConnection,
Action<P2PEndpoint, PeerDisconnectPacket> OnConnectionClosed,
Action<P2PEndpoint, IReadMessage> OnData);
protected readonly Callbacks callbacks;
protected P2PSocket(Callbacks callbacks)
{
this.callbacks = callbacks;
}
public abstract void ProcessIncomingMessages();
public abstract bool SendMessage(P2PEndpoint endpoint, IWriteMessage outMsg, DeliveryMethod deliveryMethod);
public abstract void CloseConnection(P2PEndpoint endpoint);
public abstract void Dispose();
}
@@ -0,0 +1,114 @@
using System;
using System.Runtime.InteropServices;
using Barotrauma.Steam;
namespace Barotrauma.Networking;
sealed class SteamConnectSocket : P2PSocket
{
private sealed class ConnectionManager : Steamworks.ConnectionManager, Steamworks.IConnectionManager
{
private SteamP2PEndpoint endpoint;
private Callbacks callbacks;
public void SetEndpointAndCallbacks(SteamP2PEndpoint endpoint, Callbacks callbacks)
{
this.endpoint = endpoint;
this.callbacks = callbacks;
}
public override void OnMessage(IntPtr data, int size, long messageNum, long recvTime, int channel)
{
var dataArray = new byte[size];
Marshal.Copy(source: data, destination: dataArray, startIndex: 0, length: size);
callbacks.OnData(endpoint, new ReadWriteMessage(dataArray, bitPos: 0, lBits: size * 8, copyBuf: false));
}
public override void OnDisconnected(Steamworks.Data.ConnectionInfo info)
{
if (!info.Identity.IsSteamId) { return; }
var remoteEndpoint = new SteamP2PEndpoint(new SteamId((Steamworks.SteamId)info.Identity));
var peerDisconnectPacket = PeerDisconnectPacket.WithReason(info.EndReason switch
{
Steamworks.NetConnectionEnd.App_Generic => DisconnectReason.Disconnected,
Steamworks.NetConnectionEnd.AppException_Generic => DisconnectReason.Unknown,
Steamworks.NetConnectionEnd.Local_OfflineMode => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Local_ManyRelayConnectivity => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Local_HostedServerPrimaryRelay => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Local_NetworkConfig => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Local_Rights => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Local_P2P_ICE_NoPublicAddresses => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Remote_Timeout => DisconnectReason.SteamP2PTimeOut,
Steamworks.NetConnectionEnd.Remote_BadCrypt => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Remote_BadCert => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Remote_BadProtocolVersion => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Remote_P2P_ICE_NoPublicAddresses => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Misc_Generic => DisconnectReason.Unknown,
Steamworks.NetConnectionEnd.Misc_InternalError => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Misc_Timeout => DisconnectReason.SteamP2PTimeOut,
Steamworks.NetConnectionEnd.Misc_SteamConnectivity => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Misc_NoRelaySessionsToClient => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Misc_P2P_Rendezvous => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Misc_P2P_NAT_Firewall => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Misc_PeerSentNoConnection => DisconnectReason.SteamP2PError,
_ => DisconnectReason.Unknown
});
callbacks.OnConnectionClosed(remoteEndpoint, peerDisconnectPacket);
base.OnDisconnected(info);
}
}
private readonly SteamP2PEndpoint expectedEndpoint;
private readonly ConnectionManager connectionManager;
private SteamConnectSocket(SteamP2PEndpoint expectedEndpoint, Callbacks callbacks, ConnectionManager connectionManager) : base(callbacks)
{
this.expectedEndpoint = expectedEndpoint;
this.connectionManager = connectionManager;
}
public static Result<P2PSocket, Error> Create(SteamP2PEndpoint endpoint, Callbacks callbacks)
{
if (!SteamManager.IsInitialized) { return Result.Failure(new Error(ErrorCode.SteamNotInitialized)); }
var connectionManager = Steamworks.SteamNetworkingSockets.ConnectRelay<ConnectionManager>(endpoint.SteamId.Value);
if (connectionManager is null) { return Result.Failure(new Error(ErrorCode.FailedToCreateSteamP2PSocket)); }
connectionManager.SetEndpointAndCallbacks(endpoint, callbacks);
return Result.Success((P2PSocket)new SteamConnectSocket(endpoint, callbacks, connectionManager));
}
public override void ProcessIncomingMessages()
{
connectionManager.Receive();
}
public override bool SendMessage(P2PEndpoint endpoint, IWriteMessage outMsg, DeliveryMethod deliveryMethod)
{
if (endpoint != expectedEndpoint) { return false; }
var result = connectionManager.Connection.SendMessage(
data: outMsg.Buffer,
offset: 0,
length: outMsg.LengthBytes,
sendType: deliveryMethod switch
{
DeliveryMethod.Reliable => Steamworks.Data.SendType.Reliable,
_ => Steamworks.Data.SendType.Unreliable
});
return result == Steamworks.Result.OK;
}
public override void CloseConnection(P2PEndpoint endpoint)
{
if (endpoint != expectedEndpoint) { return; }
connectionManager.Close();
}
public override void Dispose()
{
connectionManager.Close();
}
}
@@ -0,0 +1,148 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Barotrauma.Steam;
namespace Barotrauma.Networking;
sealed class SteamListenSocket : P2PSocket
{
private sealed class SocketManager : Steamworks.SocketManager, Steamworks.ISocketManager
{
private Callbacks callbacks;
private readonly Dictionary<SteamP2PEndpoint, Steamworks.Data.Connection> endpointToConnection = new();
public void SetCallbacks(Callbacks callbacks)
{
this.callbacks = callbacks;
}
public override void OnConnecting(Steamworks.Data.Connection connection, Steamworks.Data.ConnectionInfo info)
{
if (!info.Identity.IsSteamId) { return; }
var remoteEndpoint = new SteamP2PEndpoint(new SteamId((Steamworks.SteamId)info.Identity));
endpointToConnection[remoteEndpoint] = connection;
if (callbacks.OnIncomingConnection(remoteEndpoint))
{
connection.Accept();
}
}
public override void OnDisconnected(Steamworks.Data.Connection connection, Steamworks.Data.ConnectionInfo info)
{
if (!info.Identity.IsSteamId) { return; }
var remoteEndpoint = new SteamP2PEndpoint(new SteamId((Steamworks.SteamId)info.Identity));
endpointToConnection.Remove(remoteEndpoint);
var peerDisconnectPacket = PeerDisconnectPacket.WithReason(info.EndReason switch
{
Steamworks.NetConnectionEnd.App_Generic => DisconnectReason.Disconnected,
Steamworks.NetConnectionEnd.AppException_Generic => DisconnectReason.Unknown,
Steamworks.NetConnectionEnd.Local_OfflineMode => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Local_ManyRelayConnectivity => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Local_HostedServerPrimaryRelay => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Local_NetworkConfig => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Local_Rights => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Local_P2P_ICE_NoPublicAddresses => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Remote_Timeout => DisconnectReason.SteamP2PTimeOut,
Steamworks.NetConnectionEnd.Remote_BadCrypt => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Remote_BadCert => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Remote_BadProtocolVersion => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Remote_P2P_ICE_NoPublicAddresses => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Misc_Generic => DisconnectReason.Unknown,
Steamworks.NetConnectionEnd.Misc_InternalError => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Misc_Timeout => DisconnectReason.SteamP2PTimeOut,
Steamworks.NetConnectionEnd.Misc_SteamConnectivity => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Misc_NoRelaySessionsToClient => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Misc_P2P_Rendezvous => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Misc_P2P_NAT_Firewall => DisconnectReason.SteamP2PError,
Steamworks.NetConnectionEnd.Misc_PeerSentNoConnection => DisconnectReason.SteamP2PError,
_ => DisconnectReason.Unknown
});
callbacks.OnConnectionClosed(remoteEndpoint, peerDisconnectPacket);
base.OnDisconnected(connection, info);
}
public override void OnMessage(Steamworks.Data.Connection connection, Steamworks.Data.NetIdentity identity, IntPtr data, int size, long messageNum, long recvTime, int channel)
{
if (!identity.IsSteamId) { return; }
var endpoint = new SteamP2PEndpoint(new SteamId((Steamworks.SteamId)identity));
var dataArray = new byte[size];
Marshal.Copy(source: data, destination: dataArray, startIndex: 0, length: size);
callbacks.OnData(endpoint, new ReadWriteMessage(dataArray, bitPos: 0, lBits: size * 8, copyBuf: false));
}
internal bool SendMessage(SteamP2PEndpoint endpoint, IWriteMessage outMsg, DeliveryMethod deliveryMethod)
{
if (!endpointToConnection.TryGetValue(endpoint, out var connection))
{
return false;
}
var result = connection.SendMessage(
data: outMsg.Buffer,
offset: 0,
length: outMsg.LengthBytes,
sendType: deliveryMethod switch
{
DeliveryMethod.Reliable => Steamworks.Data.SendType.Reliable,
_ => Steamworks.Data.SendType.Unreliable
});
return result == Steamworks.Result.OK;
}
internal void CloseConnection(SteamP2PEndpoint endpoint)
{
if (!endpointToConnection.TryGetValue(endpoint, out var connection)) { return; }
connection.Close();
}
}
private readonly SocketManager socketManager;
private SteamListenSocket(
Callbacks callbacks,
SocketManager socketManager)
: base(callbacks)
{
this.socketManager = socketManager;
}
public static Result<P2PSocket, Error> Create(Callbacks callbacks)
{
if (!SteamManager.IsInitialized) { return Result.Failure(new Error(ErrorCode.SteamNotInitialized)); }
var socketManager = Steamworks.SteamNetworkingSockets.CreateRelaySocket<SocketManager>();
if (socketManager is null) { return Result.Failure(new Error(ErrorCode.FailedToCreateSteamP2PSocket)); }
socketManager.SetCallbacks(callbacks);
return Result.Success((P2PSocket)new SteamListenSocket(callbacks, socketManager));
}
public override void ProcessIncomingMessages()
{
socketManager.Receive();
}
public override bool SendMessage(P2PEndpoint endpoint, IWriteMessage outMsg, DeliveryMethod deliveryMethod)
{
if (endpoint is not SteamP2PEndpoint steamP2PEndpoint) { return false; }
return socketManager.SendMessage(steamP2PEndpoint, outMsg, deliveryMethod);
}
public override void CloseConnection(P2PEndpoint endpoint)
{
if (endpoint is not SteamP2PEndpoint steamP2PEndpoint) { return; }
socketManager.CloseConnection(steamP2PEndpoint);
}
public override void Dispose()
{
socketManager.Close();
}
}