Merge branch 'master' of https://github.com/Regalis11/Barotrauma into develop
This commit is contained in:
@@ -26,7 +26,9 @@ namespace Barotrauma.Networking
|
||||
|
||||
PrivateStart();
|
||||
|
||||
processInfo.Arguments += " -pipes " + writePipe.GetClientHandleAsString() + " " + readPipe.GetClientHandleAsString();
|
||||
processInfo.ArgumentList.Add("-pipes");
|
||||
processInfo.ArgumentList.Add(writePipe.GetClientHandleAsString());
|
||||
processInfo.ArgumentList.Add(readPipe.GetClientHandleAsString());
|
||||
try
|
||||
{
|
||||
Process = Process.Start(processInfo);
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Steam;
|
||||
|
||||
namespace Barotrauma.Networking;
|
||||
|
||||
readonly record struct ConnectCommand(
|
||||
Option<ConnectCommand.NameAndP2PEndpoints> NameAndP2PEndpointsOption,
|
||||
Option<ConnectCommand.NameAndLidgrenEndpoint> NameAndLidgrenEndpointOption,
|
||||
Option<ConnectCommand.SteamLobbyId> SteamLobbyIdOption)
|
||||
{
|
||||
public bool IsClientConnectedToEndpoint()
|
||||
{
|
||||
if (GameMain.Client?.ClientPeer == null) { return false; }
|
||||
if (NameAndP2PEndpointsOption.TryUnwrap(out var nameAndP2PEndpoints))
|
||||
{
|
||||
if (nameAndP2PEndpoints.Endpoints.Any(e => e.Equals(GameMain.Client.ClientPeer.ServerEndpoint))) { return true; }
|
||||
}
|
||||
if (NameAndLidgrenEndpointOption.TryUnwrap(out var nameAndLidgrenEndpoint))
|
||||
{
|
||||
if (nameAndLidgrenEndpoint.Endpoint.Equals(GameMain.Client.ClientPeer.ServerEndpoint)) { return true; }
|
||||
}
|
||||
if (SteamLobbyIdOption.TryUnwrap(out var steamLobbyId))
|
||||
{
|
||||
if (SteamManager.CurrentLobbyID == steamLobbyId.Value) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public readonly record struct NameAndP2PEndpoints(
|
||||
string ServerName,
|
||||
ImmutableArray<P2PEndpoint> Endpoints);
|
||||
|
||||
public readonly record struct NameAndLidgrenEndpoint(
|
||||
string ServerName,
|
||||
LidgrenEndpoint Endpoint);
|
||||
|
||||
public readonly record struct SteamLobbyId(ulong Value);
|
||||
|
||||
public ConnectCommand(string serverName, Endpoint endpoint)
|
||||
: this(
|
||||
NameAndP2PEndpointsOption: endpoint is P2PEndpoint p2pEndpoint
|
||||
? Option.Some(new NameAndP2PEndpoints(ServerName: serverName, p2pEndpoint.ToEnumerable().ToImmutableArray()))
|
||||
: Option.None,
|
||||
NameAndLidgrenEndpointOption: endpoint is LidgrenEndpoint lidgrenEndpoint
|
||||
? Option.Some(new NameAndLidgrenEndpoint(ServerName: serverName, lidgrenEndpoint))
|
||||
: Option.None,
|
||||
SteamLobbyIdOption: Option.None) { }
|
||||
|
||||
public ConnectCommand(string serverName, ImmutableArray<P2PEndpoint> endpoints)
|
||||
: this(
|
||||
NameAndP2PEndpointsOption: Option.Some(new NameAndP2PEndpoints(ServerName: serverName, Endpoints: endpoints)),
|
||||
NameAndLidgrenEndpointOption: Option.None,
|
||||
SteamLobbyIdOption: Option.None) { }
|
||||
|
||||
public ConnectCommand(string serverName, LidgrenEndpoint endpoint)
|
||||
: this(
|
||||
NameAndP2PEndpointsOption: Option.None,
|
||||
NameAndLidgrenEndpointOption: Option.Some(new NameAndLidgrenEndpoint(ServerName: serverName, Endpoint: endpoint)),
|
||||
SteamLobbyIdOption: Option.None) { }
|
||||
|
||||
public ConnectCommand(SteamLobbyId lobbyId)
|
||||
: this(
|
||||
NameAndP2PEndpointsOption: Option.None,
|
||||
NameAndLidgrenEndpointOption: Option.None,
|
||||
SteamLobbyIdOption: Option.Some(lobbyId)) { }
|
||||
|
||||
public static Option<ConnectCommand> Parse(string str)
|
||||
=> Parse(ToolBox.SplitCommand(str));
|
||||
|
||||
public static Option<ConnectCommand> Parse(IReadOnlyList<string>? args)
|
||||
{
|
||||
if (args == null || args.Count < 2) { return Option.None; }
|
||||
|
||||
if (args[0].Equals("-connect", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (args.Count < 3) { return Option.None; }
|
||||
|
||||
var serverName = args[1];
|
||||
|
||||
var endpointStrs = args[2].Split(",");
|
||||
var endpoints = endpointStrs.Select(Endpoint.Parse).NotNone().ToImmutableArray();
|
||||
if (endpoints.Length != endpointStrs.Length) { return Option.None; }
|
||||
|
||||
if (endpoints.All(e => e is P2PEndpoint))
|
||||
{
|
||||
return Option.Some(
|
||||
new ConnectCommand(serverName, endpoints.Cast<P2PEndpoint>().ToImmutableArray()));
|
||||
}
|
||||
else if (endpoints.Length == 1 && endpoints[0] is LidgrenEndpoint lidgrenEndpoint)
|
||||
{
|
||||
return Option.Some(
|
||||
new ConnectCommand(serverName, lidgrenEndpoint));
|
||||
}
|
||||
|
||||
return Option.None;
|
||||
}
|
||||
else if (args[0].Equals("+connect_lobby", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return UInt64.TryParse(args[1], out var lobbyId)
|
||||
? Option.Some(new ConnectCommand(new SteamLobbyId(lobbyId)))
|
||||
: Option.None;
|
||||
}
|
||||
return Option.None;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (SteamLobbyIdOption.TryUnwrap(out var steamLobbyId))
|
||||
{
|
||||
return $"+connect_lobby {steamLobbyId.Value}";
|
||||
}
|
||||
|
||||
if (NameAndP2PEndpointsOption.TryUnwrap(out var nameAndP2PEndpoints))
|
||||
{
|
||||
var escapedName = nameAndP2PEndpoints.ServerName.Replace("\"", "\\\"");
|
||||
var escapedEndpoints = nameAndP2PEndpoints.Endpoints.Select(e => e.StringRepresentation).JoinEscaped(',');
|
||||
return $"-connect \"{escapedName}\" {escapedEndpoints}";
|
||||
}
|
||||
|
||||
if (NameAndLidgrenEndpointOption.TryUnwrap(out var nameAndLidgrenEndpoint))
|
||||
{
|
||||
var escapedName = nameAndLidgrenEndpoint.ServerName.Replace("\"", "\\\"");
|
||||
var endpoint = nameAndLidgrenEndpoint.Endpoint.StringRepresentation;
|
||||
return $"-connect \"{escapedName}\" {endpoint}";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -162,7 +162,7 @@ namespace Barotrauma.Networking
|
||||
set;
|
||||
}
|
||||
|
||||
private readonly Endpoint serverEndpoint;
|
||||
private readonly ImmutableArray<Endpoint> serverEndpoints;
|
||||
private readonly Option<int> ownerKey;
|
||||
|
||||
public bool IsServerOwner => ownerKey.IsSome();
|
||||
@@ -182,6 +182,9 @@ namespace Barotrauma.Networking
|
||||
public readonly NamedEvent<PermissionChangedEvent> OnPermissionChanged = new NamedEvent<PermissionChangedEvent>();
|
||||
|
||||
public GameClient(string newName, Endpoint endpoint, string serverName, Option<int> ownerKey)
|
||||
: this(newName, endpoint.ToEnumerable().ToImmutableArray(), serverName, ownerKey) { }
|
||||
|
||||
public GameClient(string newName, ImmutableArray<Endpoint> endpoints, string serverName, Option<int> ownerKey)
|
||||
{
|
||||
//TODO: gui stuff should probably not be here?
|
||||
this.ownerKey = ownerKey;
|
||||
@@ -270,7 +273,7 @@ namespace Barotrauma.Networking
|
||||
ServerSettings = new ServerSettings(this, "Server", 0, 0, 0, false, false, System.Net.IPAddress.Any);
|
||||
Voting = new Voting();
|
||||
|
||||
serverEndpoint = endpoint;
|
||||
serverEndpoints = endpoints;
|
||||
InitiateServerJoin(serverName);
|
||||
|
||||
//ServerLog = new ServerLog("");
|
||||
@@ -281,7 +284,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
public ServerInfo CreateServerInfoFromSettings()
|
||||
{
|
||||
var serverInfo = ServerInfo.FromServerConnection(ClientPeer.ServerConnection, ServerSettings);
|
||||
var serverInfo = ServerInfo.FromServerEndpoints(ClientPeer.AllServerEndpoints, ServerSettings);
|
||||
GameMain.ServerListScreen.UpdateOrAddServerInfo(serverInfo);
|
||||
return serverInfo;
|
||||
}
|
||||
@@ -327,11 +330,14 @@ namespace Barotrauma.Networking
|
||||
ReadDataMessage,
|
||||
OnClientPeerDisconnect,
|
||||
OnConnectionInitializationComplete);
|
||||
return serverEndpoint switch
|
||||
{
|
||||
LidgrenEndpoint lidgrenEndpoint => new LidgrenClientPeer(lidgrenEndpoint, callbacks, ownerKey),
|
||||
SteamP2PEndpoint _ when ownerKey.TryUnwrap(out var key) => new SteamP2POwnerPeer(callbacks, key),
|
||||
SteamP2PEndpoint steamP2PServerEndpoint when ownerKey.IsNone() => new SteamP2PClientPeer(steamP2PServerEndpoint, callbacks),
|
||||
return serverEndpoints.First() switch
|
||||
{
|
||||
LidgrenEndpoint lidgrenEndpoint
|
||||
=> new LidgrenClientPeer(lidgrenEndpoint, callbacks, ownerKey),
|
||||
P2PEndpoint when ownerKey.TryUnwrap(out int key)
|
||||
=> new P2POwnerPeer(callbacks, key, serverEndpoints.Cast<P2PEndpoint>().ToImmutableArray()),
|
||||
P2PEndpoint when ownerKey.IsNone()
|
||||
=> new P2PClientPeer(serverEndpoints.Cast<P2PEndpoint>().ToImmutableArray(), callbacks),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
@@ -802,6 +808,9 @@ namespace Barotrauma.Networking
|
||||
case ServerPacketHeader.ACHIEVEMENT:
|
||||
ReadAchievement(inc);
|
||||
break;
|
||||
case ServerPacketHeader.ACHIEVEMENT_STAT:
|
||||
ReadAchievementStat(inc);
|
||||
break;
|
||||
case ServerPacketHeader.CHEATS_ENABLED:
|
||||
bool cheatsEnabled = inc.ReadBoolean();
|
||||
inc.ReadPadBits();
|
||||
@@ -812,7 +821,7 @@ namespace Barotrauma.Networking
|
||||
else
|
||||
{
|
||||
DebugConsole.CheatsEnabled = cheatsEnabled;
|
||||
SteamAchievementManager.CheatsEnabled = cheatsEnabled;
|
||||
AchievementManager.CheatsEnabled = cheatsEnabled;
|
||||
if (cheatsEnabled)
|
||||
{
|
||||
var cheatMessageBox = new GUIMessageBox(TextManager.Get("CheatsEnabledTitle"), TextManager.Get("CheatsEnabledDescription"));
|
||||
@@ -860,7 +869,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
private void ReadStartGameFinalize(IReadMessage inc)
|
||||
{
|
||||
TaskPool.ListTasks();
|
||||
TaskPool.ListTasks(DebugConsole.Log);
|
||||
ushort contentToPreloadCount = inc.ReadUInt16();
|
||||
List<ContentFile> contentToPreload = new List<ContentFile>();
|
||||
for (int i = 0; i < contentToPreloadCount; i++)
|
||||
@@ -997,8 +1006,9 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ClientPeer is SteamP2PClientPeer or SteamP2POwnerPeer)
|
||||
if (ClientPeer is P2PClientPeer or P2POwnerPeer)
|
||||
{
|
||||
Eos.EosSessionManager.LeaveSession();
|
||||
SteamManager.LeaveLobby();
|
||||
}
|
||||
|
||||
@@ -1007,10 +1017,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
GameMain.GameSession?.Campaign?.CancelStartRound();
|
||||
|
||||
if (SteamManager.IsInitialized)
|
||||
{
|
||||
Steamworks.SteamFriends.ClearRichPresence();
|
||||
}
|
||||
UpdatePresence("");
|
||||
foreach (var fileTransfer in FileReceiver.ActiveTransfers.ToArray())
|
||||
{
|
||||
FileReceiver.StopTransfer(fileTransfer, deleteFile: true);
|
||||
@@ -1099,19 +1106,47 @@ namespace Barotrauma.Networking
|
||||
ClientPeer.ServerContentPackages = prevContentPackages;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnConnectionInitializationComplete()
|
||||
|
||||
private void UpdatePresence(string connectCommand)
|
||||
{
|
||||
#warning TODO: use store localization functionality
|
||||
var desc = TextManager.GetWithVariable("FriendPlayingOnServer", "[servername]", ServerName);
|
||||
|
||||
async Task updateEosPresence()
|
||||
{
|
||||
var epicIds = EosInterface.IdQueries.GetLoggedInEpicIds();
|
||||
if (!epicIds.FirstOrNone().TryUnwrap(out var epicAccountId)) { return; }
|
||||
|
||||
var setPresenceResult = await EosInterface.Presence.SetJoinCommand(
|
||||
epicAccountId: epicAccountId,
|
||||
desc: desc.Value,
|
||||
serverName: ServerName,
|
||||
joinCommand: connectCommand);
|
||||
DebugConsole.NewMessage($"Set connect command: {connectCommand}, result: {setPresenceResult}");
|
||||
}
|
||||
|
||||
TaskPool.Add(
|
||||
"UpdateEosPresence",
|
||||
updateEosPresence(),
|
||||
static _ => { });
|
||||
|
||||
if (SteamManager.IsInitialized)
|
||||
{
|
||||
Steamworks.SteamFriends.ClearRichPresence();
|
||||
Steamworks.SteamFriends.SetRichPresence("servername", ServerName);
|
||||
#warning TODO: use Steamworks localization functionality
|
||||
Steamworks.SteamFriends.SetRichPresence("status",
|
||||
TextManager.GetWithVariable("FriendPlayingOnServer", "[servername]", ServerName).Value);
|
||||
Steamworks.SteamFriends.SetRichPresence("connect",
|
||||
$"-connect \"{ToolBox.EscapeCharacters(ServerName)}\" {serverEndpoint}");
|
||||
if (!connectCommand.IsNullOrWhiteSpace())
|
||||
{
|
||||
Steamworks.SteamFriends.SetRichPresence("servername", ServerName);
|
||||
Steamworks.SteamFriends.SetRichPresence("status",
|
||||
desc.Value);
|
||||
Steamworks.SteamFriends.SetRichPresence("connect",
|
||||
connectCommand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnConnectionInitializationComplete()
|
||||
{
|
||||
UpdatePresence($"-connect \"{ToolBox.EscapeCharacters(ServerName)}\" {string.Join(",", serverEndpoints.Select(e => e.StringRepresentation))}");
|
||||
|
||||
canStart = true;
|
||||
connected = true;
|
||||
@@ -1169,18 +1204,16 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
|
||||
private void ReadAchievement(IReadMessage inc)
|
||||
private static void ReadAchievement(IReadMessage inc)
|
||||
{
|
||||
Identifier achievementIdentifier = inc.ReadIdentifier();
|
||||
int amount = inc.ReadInt32();
|
||||
if (amount == 0)
|
||||
{
|
||||
SteamAchievementManager.UnlockAchievement(achievementIdentifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
SteamAchievementManager.IncrementStat(achievementIdentifier, amount);
|
||||
}
|
||||
AchievementManager.UnlockAchievement(achievementIdentifier);
|
||||
}
|
||||
|
||||
private static void ReadAchievementStat(IReadMessage inc)
|
||||
{
|
||||
var netStat = INetSerializableStruct.Read<NetIncrementedStat>(inc);
|
||||
AchievementManager.IncrementStat(netStat.Stat, netStat.Amount);
|
||||
}
|
||||
|
||||
private static void ReadCircuitBoxMessage(IReadMessage inc)
|
||||
@@ -1887,8 +1920,9 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
if (updateClientListId) { LastClientListUpdateID = listId; }
|
||||
|
||||
if (ClientPeer is SteamP2POwnerPeer)
|
||||
if (ClientPeer is P2POwnerPeer)
|
||||
{
|
||||
Eos.EosSessionManager.UpdateOwnedSession(ClientPeer.ServerConnection.Endpoint, ServerSettings);
|
||||
TaskPool.Add("WaitForPingDataAsync (owner)",
|
||||
Steamworks.SteamNetworkingUtils.WaitForPingDataAsync(), (task) =>
|
||||
{
|
||||
@@ -2031,8 +2065,9 @@ namespace Barotrauma.Networking
|
||||
ServerSettings.AllowSubVoting = allowSubVoting;
|
||||
ServerSettings.AllowModeVoting = allowModeVoting;
|
||||
|
||||
if (ClientPeer is SteamP2POwnerPeer)
|
||||
if (ClientPeer is P2POwnerPeer)
|
||||
{
|
||||
Eos.EosSessionManager.UpdateOwnedSession(ClientPeer.ServerConnection.Endpoint, ServerSettings);
|
||||
Steam.SteamManager.UpdateLobby(ServerSettings);
|
||||
}
|
||||
|
||||
|
||||
+82
@@ -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(); }
|
||||
}
|
||||
}
|
||||
+99
@@ -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();
|
||||
}
|
||||
+114
@@ -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();
|
||||
}
|
||||
}
|
||||
+148
@@ -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();
|
||||
}
|
||||
}
|
||||
+59
-30
@@ -1,12 +1,18 @@
|
||||
#nullable enable
|
||||
using Barotrauma.Steam;
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
internal abstract class ClientPeer<TEndpoint> : ClientPeer where TEndpoint : Endpoint
|
||||
{
|
||||
public new TEndpoint ServerEndpoint => (base.ServerEndpoint as TEndpoint)!;
|
||||
protected ClientPeer(TEndpoint serverEndpoint, ImmutableArray<Endpoint> allServerEndpoints, Callbacks callbacks, Option<int> ownerKey)
|
||||
: base(serverEndpoint, allServerEndpoints, callbacks, ownerKey) { }
|
||||
}
|
||||
|
||||
internal abstract class ClientPeer
|
||||
{
|
||||
public ImmutableArray<ServerContentPackage> ServerContentPackages { get; set; } =
|
||||
@@ -25,21 +31,22 @@ namespace Barotrauma.Networking
|
||||
protected readonly Callbacks callbacks;
|
||||
|
||||
public readonly Endpoint ServerEndpoint;
|
||||
public readonly ImmutableArray<Endpoint> AllServerEndpoints;
|
||||
public NetworkConnection? ServerConnection { get; protected set; }
|
||||
|
||||
protected readonly bool isOwner;
|
||||
protected bool IsOwner => ownerKey.IsSome();
|
||||
protected readonly Option<int> ownerKey;
|
||||
|
||||
public bool IsActive => isActive;
|
||||
|
||||
protected bool isActive;
|
||||
|
||||
public ClientPeer(Endpoint serverEndpoint, Callbacks callbacks, Option<int> ownerKey)
|
||||
protected ClientPeer(Endpoint serverEndpoint, ImmutableArray<Endpoint> allServerEndpoints, Callbacks callbacks, Option<int> ownerKey)
|
||||
{
|
||||
ServerEndpoint = serverEndpoint;
|
||||
AllServerEndpoints = allServerEndpoints;
|
||||
this.callbacks = callbacks;
|
||||
this.ownerKey = ownerKey;
|
||||
isOwner = ownerKey.IsSome();
|
||||
}
|
||||
|
||||
public abstract void Start();
|
||||
@@ -53,7 +60,7 @@ namespace Barotrauma.Networking
|
||||
protected ConnectionInitialization initializationStep;
|
||||
public bool ContentPackageOrderReceived { get; set; }
|
||||
protected int passwordSalt;
|
||||
protected Option<Steamworks.AuthTicket> steamAuthTicket;
|
||||
protected Option<AuthenticationTicket> authTicket;
|
||||
private GUIMessageBox? passwordMsgBox;
|
||||
|
||||
public bool WaitingForPassword
|
||||
@@ -67,43 +74,64 @@ namespace Barotrauma.Networking
|
||||
public IReadMessage Message;
|
||||
}
|
||||
|
||||
protected abstract Task<Option<AccountId>> GetAccountId();
|
||||
|
||||
protected void OnInitializationComplete()
|
||||
{
|
||||
passwordMsgBox?.Close();
|
||||
if (initializationStep == ConnectionInitialization.Success) { return; }
|
||||
|
||||
callbacks.OnInitializationComplete.Invoke();
|
||||
initializationStep = ConnectionInitialization.Success;
|
||||
}
|
||||
|
||||
protected void ReadConnectionInitializationStep(IncomingInitializationMessage inc)
|
||||
{
|
||||
if (inc.InitializationStep != ConnectionInitialization.Password)
|
||||
{
|
||||
passwordMsgBox?.Close();
|
||||
}
|
||||
|
||||
switch (inc.InitializationStep)
|
||||
{
|
||||
case ConnectionInitialization.SteamTicketAndVersion:
|
||||
case ConnectionInitialization.AuthInfoAndVersion:
|
||||
{
|
||||
if (initializationStep != ConnectionInitialization.SteamTicketAndVersion) { return; }
|
||||
if (initializationStep != ConnectionInitialization.AuthInfoAndVersion) { return; }
|
||||
|
||||
PeerPacketHeaders headers = new PeerPacketHeaders
|
||||
TaskPool.Add($"{GetType().Name}.{nameof(GetAccountId)}", GetAccountId(), t =>
|
||||
{
|
||||
DeliveryMethod = DeliveryMethod.Reliable,
|
||||
PacketHeader = PacketHeader.IsConnectionInitializationStep,
|
||||
Initialization = ConnectionInitialization.SteamTicketAndVersion
|
||||
};
|
||||
if (GameMain.Client?.ClientPeer is null) { return; }
|
||||
|
||||
if (!t.TryGetResult(out Option<AccountId> accountId))
|
||||
{
|
||||
Close(PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationFailed));
|
||||
}
|
||||
|
||||
var headers = new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = DeliveryMethod.Reliable,
|
||||
PacketHeader = PacketHeader.IsConnectionInitializationStep,
|
||||
Initialization = ConnectionInitialization.AuthInfoAndVersion
|
||||
};
|
||||
|
||||
if (steamAuthTicket.TryUnwrap(out var authTicket) && authTicket is { Canceled: true })
|
||||
{
|
||||
throw new InvalidOperationException("ReadConnectionInitializationStep failed: Steam auth ticket has been cancelled.");
|
||||
}
|
||||
var body = new ClientAuthTicketAndVersionPacket
|
||||
{
|
||||
Name = GameMain.Client.Name,
|
||||
OwnerKey = ownerKey,
|
||||
AccountId = accountId,
|
||||
AuthTicket = authTicket,
|
||||
GameVersion = GameMain.Version.ToString(),
|
||||
Language = GameSettings.CurrentConfig.Language.Value
|
||||
};
|
||||
|
||||
ClientSteamTicketAndVersionPacket body = new ClientSteamTicketAndVersionPacket
|
||||
{
|
||||
Name = GameMain.Client.Name,
|
||||
OwnerKey = ownerKey,
|
||||
SteamId = SteamManager.GetSteamId().Select(id => (AccountId)id),
|
||||
SteamAuthTicket = steamAuthTicket.Bind(t => t.Data != null ? Option.Some(t.Data) : Option.None),
|
||||
GameVersion = GameMain.Version.ToString(),
|
||||
Language = GameSettings.CurrentConfig.Language.Value
|
||||
};
|
||||
|
||||
SendMsgInternal(headers, body);
|
||||
SendMsgInternal(headers, body);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case ConnectionInitialization.ContentPackageOrder:
|
||||
{
|
||||
if (initializationStep
|
||||
is ConnectionInitialization.SteamTicketAndVersion
|
||||
is ConnectionInitialization.AuthInfoAndVersion
|
||||
or ConnectionInitialization.Password)
|
||||
{
|
||||
initializationStep = ConnectionInitialization.ContentPackageOrder;
|
||||
@@ -136,7 +164,7 @@ namespace Barotrauma.Networking
|
||||
break;
|
||||
}
|
||||
case ConnectionInitialization.Password:
|
||||
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion)
|
||||
if (initializationStep == ConnectionInitialization.AuthInfoAndVersion)
|
||||
{
|
||||
initializationStep = ConnectionInitialization.Password;
|
||||
}
|
||||
@@ -152,6 +180,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
LocalizedString pwMsg = TextManager.Get("PasswordRequired");
|
||||
|
||||
passwordMsgBox?.Close();
|
||||
passwordMsgBox = new GUIMessageBox(pwMsg, "", new LocalizedString[] { TextManager.Get("OK"), TextManager.Get("Cancel") },
|
||||
relativeSize: new Vector2(0.25f, 0.1f), minSize: new Point(400, GUI.IntScale(170)));
|
||||
var passwordHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), passwordMsgBox.Content.RectTransform), childAnchor: Anchor.TopCenter);
|
||||
|
||||
+46
-34
@@ -1,26 +1,25 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Barotrauma.Extensions;
|
||||
using Lidgren.Network;
|
||||
using Barotrauma.Steam;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
internal sealed class LidgrenClientPeer : ClientPeer
|
||||
internal sealed class LidgrenClientPeer : ClientPeer<LidgrenEndpoint>
|
||||
{
|
||||
private NetClient? netClient;
|
||||
private readonly NetPeerConfiguration netPeerConfiguration;
|
||||
|
||||
private readonly List<NetIncomingMessage> incomingLidgrenMessages;
|
||||
|
||||
private LidgrenEndpoint lidgrenEndpoint =>
|
||||
ServerConnection is LidgrenConnection { Endpoint: LidgrenEndpoint result }
|
||||
? result
|
||||
: throw new InvalidOperationException();
|
||||
|
||||
public LidgrenClientPeer(LidgrenEndpoint endpoint, Callbacks callbacks, Option<int> ownerKey) : base(endpoint, callbacks, ownerKey)
|
||||
public LidgrenClientPeer(LidgrenEndpoint endpoint, Callbacks callbacks, Option<int> ownerKey) : base(endpoint, ((Endpoint)endpoint).ToEnumerable().ToImmutableArray(), callbacks, ownerKey)
|
||||
{
|
||||
ServerConnection = null;
|
||||
|
||||
@@ -56,18 +55,9 @@ namespace Barotrauma.Networking
|
||||
|
||||
netClient = new NetClient(netPeerConfiguration);
|
||||
|
||||
if (SteamManager.IsInitialized)
|
||||
{
|
||||
steamAuthTicket = SteamManager.GetAuthSessionTicketForMultiplayer(ServerEndpoint);
|
||||
if (steamAuthTicket.IsNone())
|
||||
{
|
||||
throw new Exception("GetAuthSessionTicket returned null");
|
||||
}
|
||||
}
|
||||
initializationStep = ConnectionInitialization.AuthInfoAndVersion;
|
||||
|
||||
initializationStep = ConnectionInitialization.SteamTicketAndVersion;
|
||||
|
||||
if (!(ServerEndpoint is LidgrenEndpoint lidgrenEndpointValue))
|
||||
if (ServerEndpoint is not { } lidgrenEndpointValue)
|
||||
{
|
||||
throw new InvalidCastException($"Endpoint is not {nameof(LidgrenEndpoint)}");
|
||||
}
|
||||
@@ -79,12 +69,25 @@ namespace Barotrauma.Networking
|
||||
|
||||
netClient.Start();
|
||||
|
||||
var netConnection = netClient.Connect(lidgrenEndpointValue.NetEndpoint);
|
||||
TaskPool.Add(
|
||||
$"{nameof(LidgrenClientPeer)}.GetAuthTicket",
|
||||
AuthenticationTicket.Create(ServerEndpoint),
|
||||
t =>
|
||||
{
|
||||
if (!t.TryGetResult(out Option<AuthenticationTicket> authenticationTicket))
|
||||
{
|
||||
Close(PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationFailed));
|
||||
return;
|
||||
}
|
||||
authTicket = authenticationTicket;
|
||||
|
||||
ServerConnection = new LidgrenConnection(netConnection)
|
||||
{
|
||||
Status = NetworkConnectionStatus.Connected
|
||||
};
|
||||
var netConnection = netClient.Connect(lidgrenEndpointValue.NetEndpoint);
|
||||
|
||||
ServerConnection = new LidgrenConnection(netConnection)
|
||||
{
|
||||
Status = NetworkConnectionStatus.Connected
|
||||
};
|
||||
});
|
||||
|
||||
isActive = true;
|
||||
}
|
||||
@@ -96,7 +99,7 @@ namespace Barotrauma.Networking
|
||||
ToolBox.ThrowIfNull(netClient);
|
||||
ToolBox.ThrowIfNull(incomingLidgrenMessages);
|
||||
|
||||
if (isOwner && !ChildServerRelay.IsProcessAlive)
|
||||
if (IsOwner && !ChildServerRelay.IsProcessAlive)
|
||||
{
|
||||
var gameClient = GameMain.Client;
|
||||
Close(PeerDisconnectPacket.WithReason(DisconnectReason.ServerCrashed));
|
||||
@@ -112,9 +115,11 @@ namespace Barotrauma.Networking
|
||||
|
||||
foreach (NetIncomingMessage inc in incomingLidgrenMessages)
|
||||
{
|
||||
if (!inc.SenderConnection.RemoteEndPoint.EquivalentTo(lidgrenEndpoint.NetEndpoint))
|
||||
var remoteEndpoint = new LidgrenEndpoint(inc.SenderEndPoint);
|
||||
|
||||
if (remoteEndpoint != ServerEndpoint)
|
||||
{
|
||||
DebugConsole.AddWarning($"Mismatched endpoint: expected {lidgrenEndpoint.NetEndpoint}, got {inc.SenderConnection.RemoteEndPoint}");
|
||||
DebugConsole.AddWarning($"Mismatched endpoint: expected {ServerEndpoint.NetEndpoint}, got {inc.SenderConnection.RemoteEndPoint}");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -152,11 +157,7 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
else
|
||||
{
|
||||
if (initializationStep != ConnectionInitialization.Success)
|
||||
{
|
||||
callbacks.OnInitializationComplete.Invoke();
|
||||
initializationStep = ConnectionInitialization.Success;
|
||||
}
|
||||
OnInitializationComplete();
|
||||
|
||||
var packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
|
||||
callbacks.OnMessageReceived.Invoke(packet.GetReadMessage(packetHeader.IsCompressed(), ServerConnection));
|
||||
@@ -212,9 +213,6 @@ namespace Barotrauma.Networking
|
||||
netClient.Shutdown(peerDisconnectPacket.ToLidgrenStringRepresentation());
|
||||
netClient = null;
|
||||
|
||||
if (steamAuthTicket.TryUnwrap(out var ticket)) { ticket.Cancel(); }
|
||||
steamAuthTicket = Option.None;
|
||||
|
||||
callbacks.OnDisconnect.Invoke(peerDisconnectPacket);
|
||||
}
|
||||
|
||||
@@ -270,7 +268,21 @@ namespace Barotrauma.Networking
|
||||
return netClient.SendMessage(msg.ToLidgren(netClient), deliveryMethod.ToLidgren());
|
||||
}
|
||||
|
||||
protected override async Task<Option<AccountId>> GetAccountId()
|
||||
{
|
||||
if (!EosInterface.Core.IsInitialized) { return SteamManager.GetSteamId().Select(id => (AccountId)id); }
|
||||
|
||||
var selfPuids = EosInterface.IdQueries.GetLoggedInPuids();
|
||||
if (selfPuids.None()) { return Option.None; }
|
||||
var accountIdsResult = await EosInterface.IdQueries.GetSelfExternalAccountIds(selfPuids.First());
|
||||
return accountIdsResult.TryUnwrapSuccess(out var accountIds) && accountIds.Length > 0
|
||||
? Option.Some(accountIds[0])
|
||||
: Option.None;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
|
||||
public override void ForceTimeOut()
|
||||
{
|
||||
netClient?.ServerConnection?.ForceTimeOut();
|
||||
|
||||
+146
-128
@@ -1,136 +1,142 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Barotrauma.Steam;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Steam;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
internal sealed class SteamP2PClientPeer : ClientPeer
|
||||
internal sealed class P2PClientPeer : ClientPeer<P2PEndpoint>
|
||||
{
|
||||
private readonly SteamId hostSteamId;
|
||||
private double timeout;
|
||||
private double heartbeatTimer;
|
||||
private double connectionStatusTimer;
|
||||
|
||||
private long sentBytes, receivedBytes;
|
||||
|
||||
private readonly List<IncomingInitializationMessage> incomingInitializationMessages = new List<IncomingInitializationMessage>();
|
||||
private readonly List<IReadMessage> incomingDataMessages = new List<IReadMessage>();
|
||||
private readonly MessageFragmenter fragmenter = new();
|
||||
private readonly MessageDefragmenter defragmenter = new();
|
||||
|
||||
public SteamP2PClientPeer(SteamP2PEndpoint endpoint, Callbacks callbacks) : base(endpoint, callbacks, Option<int>.None())
|
||||
private P2PSocket? socket;
|
||||
|
||||
private static P2PEndpoint GetPrimaryEndpoint(ImmutableArray<P2PEndpoint> allEndpoints)
|
||||
{
|
||||
var steamEndpointOption = allEndpoints.OfType<SteamP2PEndpoint>().FirstOrNone();
|
||||
var eosEndpointOption = allEndpoints.OfType<EosP2PEndpoint>().FirstOrNone();
|
||||
if (SteamManager.IsInitialized)
|
||||
{
|
||||
if (steamEndpointOption.TryUnwrap(out var steamEndpoint)) { return steamEndpoint; }
|
||||
}
|
||||
if (EosInterface.Core.IsInitialized)
|
||||
{
|
||||
if (eosEndpointOption.TryUnwrap(out var eosEndpoint)) { return eosEndpoint; }
|
||||
}
|
||||
|
||||
throw new Exception($"Couldn't pick out a primary endpoint: {string.Join(", ", allEndpoints.Select(e => e.GetType().Name))}");
|
||||
}
|
||||
|
||||
public P2PClientPeer(ImmutableArray<P2PEndpoint> allEndpoints, Callbacks callbacks)
|
||||
: base(
|
||||
GetPrimaryEndpoint(allEndpoints),
|
||||
allEndpoints.Cast<Endpoint>().ToImmutableArray(),
|
||||
callbacks,
|
||||
Option.None)
|
||||
{
|
||||
ServerConnection = null;
|
||||
|
||||
isActive = false;
|
||||
|
||||
if (!(ServerEndpoint is SteamP2PEndpoint steamIdEndpoint))
|
||||
{
|
||||
throw new InvalidCastException("endPoint is not SteamId");
|
||||
}
|
||||
|
||||
hostSteamId = steamIdEndpoint.SteamId;
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
if (isActive) { return; }
|
||||
|
||||
ContentPackageOrderReceived = false;
|
||||
|
||||
steamAuthTicket = SteamManager.GetAuthSessionTicketForMultiplayer(ServerEndpoint);
|
||||
//TODO: wait for GetAuthSessionTicketResponse_t
|
||||
ServerConnection = ServerEndpoint.MakeConnectionFromEndpoint();
|
||||
|
||||
if (steamAuthTicket == null)
|
||||
var socketCallbacks = new P2PSocket.Callbacks(OnIncomingConnection, OnConnectionClosed, OnP2PData);
|
||||
var socketCreateResult = ServerEndpoint switch
|
||||
{
|
||||
throw new Exception("GetAuthSessionTicket returned null");
|
||||
}
|
||||
|
||||
Steamworks.SteamNetworking.ResetActions();
|
||||
Steamworks.SteamNetworking.OnP2PSessionRequest = OnIncomingConnection;
|
||||
Steamworks.SteamNetworking.OnP2PConnectionFailed = OnConnectionFailed;
|
||||
|
||||
Steamworks.SteamNetworking.AllowP2PPacketRelay(true);
|
||||
|
||||
ServerConnection = new SteamP2PConnection(hostSteamId);
|
||||
ServerConnection.SetAccountInfo(new AccountInfo(hostSteamId));
|
||||
|
||||
var headers = new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = DeliveryMethod.Reliable,
|
||||
PacketHeader = PacketHeader.IsConnectionInitializationStep,
|
||||
Initialization = ConnectionInitialization.ConnectionStarted
|
||||
EosP2PEndpoint => EosP2PSocket.Create(socketCallbacks),
|
||||
SteamP2PEndpoint steamP2PEndpoint => SteamConnectSocket.Create(steamP2PEndpoint, socketCallbacks),
|
||||
_ => throw new Exception($"Invalid server endpoint: {ServerEndpoint.GetType()} {ServerEndpoint}")
|
||||
};
|
||||
SendMsgInternal(headers, null);
|
||||
socket = socketCreateResult.TryUnwrapSuccess(out var s)
|
||||
? s
|
||||
: throw new Exception($"Failed to create socket for {ServerEndpoint}: {socketCreateResult}");
|
||||
TaskPool.Add(
|
||||
$"{nameof(P2PClientPeer)}.GetAuthTicket",
|
||||
AuthenticationTicket.Create(ServerEndpoint),
|
||||
t =>
|
||||
{
|
||||
if (!t.TryGetResult(out Option<AuthenticationTicket> authenticationTicket))
|
||||
{
|
||||
Close(PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationFailed));
|
||||
return;
|
||||
}
|
||||
authTicket = authenticationTicket;
|
||||
|
||||
initializationStep = ConnectionInitialization.SteamTicketAndVersion;
|
||||
var headers = new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = DeliveryMethod.Reliable,
|
||||
PacketHeader = PacketHeader.IsConnectionInitializationStep,
|
||||
Initialization = ConnectionInitialization.ConnectionStarted
|
||||
};
|
||||
SendMsgInternal(headers, null);
|
||||
});
|
||||
initializationStep = ConnectionInitialization.AuthInfoAndVersion;
|
||||
|
||||
timeout = NetworkConnection.TimeoutThreshold;
|
||||
heartbeatTimer = 1.0;
|
||||
connectionStatusTimer = 0.0;
|
||||
|
||||
isActive = true;
|
||||
}
|
||||
|
||||
private void OnIncomingConnection(Steamworks.SteamId steamId)
|
||||
private bool OnIncomingConnection(P2PEndpoint remoteEndpoint)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
if (remoteEndpoint == ServerEndpoint)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (steamId == hostSteamId.Value)
|
||||
if (initializationStep != ConnectionInitialization.Password &&
|
||||
initializationStep != ConnectionInitialization.ContentPackageOrder &&
|
||||
initializationStep != ConnectionInitialization.Success)
|
||||
{
|
||||
Steamworks.SteamNetworking.AcceptP2PSessionWithUser(steamId);
|
||||
}
|
||||
else if (initializationStep != ConnectionInitialization.Password &&
|
||||
initializationStep != ConnectionInitialization.ContentPackageOrder &&
|
||||
initializationStep != ConnectionInitialization.Success)
|
||||
{
|
||||
DebugConsole.ThrowError("Connection from incorrect SteamID was rejected: " +
|
||||
$"expected {hostSteamId}," +
|
||||
$"got {new SteamId(steamId)}");
|
||||
DebugConsole.AddWarning(
|
||||
"Connection from incorrect endpoint was rejected: " +
|
||||
$"expected {ServerEndpoint}, " +
|
||||
$"got {remoteEndpoint}");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void OnConnectionFailed(Steamworks.SteamId steamId, Steamworks.P2PSessionError error)
|
||||
private void OnConnectionClosed(P2PEndpoint remoteEndpoint, PeerDisconnectPacket peerDisconnectPacket)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
if (remoteEndpoint != ServerEndpoint) { return; }
|
||||
|
||||
if (steamId != hostSteamId.Value) { return; }
|
||||
|
||||
Close(PeerDisconnectPacket.SteamP2PError(error));
|
||||
Close(peerDisconnectPacket);
|
||||
}
|
||||
|
||||
private void OnP2PData(ulong steamId, byte[] data, int dataLength)
|
||||
private void OnP2PData(P2PEndpoint senderEndpoint, IReadMessage inc)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
if (steamId != hostSteamId.Value) { return; }
|
||||
receivedBytes += inc.LengthBytes;
|
||||
|
||||
if (senderEndpoint != ServerEndpoint) { return; }
|
||||
|
||||
timeout = Screen.Selected == GameMain.GameScreen
|
||||
? NetworkConnection.TimeoutThresholdInGame
|
||||
: NetworkConnection.TimeoutThreshold;
|
||||
|
||||
try
|
||||
{
|
||||
IReadMessage inc = new ReadOnlyMessage(data, false, 0, dataLength, ServerConnection);
|
||||
ProcessP2PData(inc);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
string errorMsg = $"Client failed to read an incoming P2P message. {{{e}}}\n{e.StackTrace.CleanupStackTrace()}";
|
||||
GameAnalyticsManager.AddErrorEventOnce($"SteamP2PClientPeer.OnP2PData:ClientReadException{e.TargetSite}", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
#else
|
||||
if (GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.ThrowError(errorMsg); }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessP2PData(IReadMessage inc)
|
||||
{
|
||||
var (deliveryMethod, packetHeader, initialization) = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
|
||||
var (_, packetHeader, initialization) = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
|
||||
|
||||
if (!packetHeader.IsServerMessage()) { return; }
|
||||
|
||||
@@ -138,9 +144,8 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (!initialization.HasValue) { return; }
|
||||
|
||||
var relayPacket = INetSerializableStruct.Read<SteamP2PInitializationRelayPacket>(inc);
|
||||
var relayPacket = INetSerializableStruct.Read<P2PInitializationRelayPacket>(inc);
|
||||
|
||||
SteamManager.JoinLobby(relayPacket.LobbyID, false);
|
||||
if (initializationStep != ConnectionInitialization.Success)
|
||||
{
|
||||
incomingInitializationMessages.Add(new IncomingInitializationMessage
|
||||
@@ -150,6 +155,14 @@ namespace Barotrauma.Networking
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (packetHeader.IsDataFragment())
|
||||
{
|
||||
var completeMessageOption = defragmenter.ProcessIncomingFragment(INetSerializableStruct.Read<MessageFragment>(inc));
|
||||
if (!completeMessageOption.TryUnwrap(out var completeMessage)) { return; }
|
||||
|
||||
int completeMessageLengthBits = completeMessage.Length * 8;
|
||||
incomingDataMessages.Add(new ReadWriteMessage(completeMessage.ToArray(), 0, completeMessageLengthBits, copyBuf: false));
|
||||
}
|
||||
else if (packetHeader.IsHeartbeatMessage())
|
||||
{
|
||||
return; //TODO: implement heartbeats
|
||||
@@ -177,41 +190,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
heartbeatTimer -= deltaTime;
|
||||
|
||||
if (initializationStep != ConnectionInitialization.Password &&
|
||||
initializationStep != ConnectionInitialization.ContentPackageOrder &&
|
||||
initializationStep != ConnectionInitialization.Success)
|
||||
{
|
||||
connectionStatusTimer -= deltaTime;
|
||||
if (connectionStatusTimer <= 0.0)
|
||||
{
|
||||
if (Steamworks.SteamNetworking.GetP2PSessionState(hostSteamId.Value) is { } state)
|
||||
{
|
||||
if (state.P2PSessionError != Steamworks.P2PSessionError.None)
|
||||
{
|
||||
Close(PeerDisconnectPacket.SteamP2PError(state.P2PSessionError));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Close(PeerDisconnectPacket.WithReason(DisconnectReason.Timeout));
|
||||
}
|
||||
|
||||
connectionStatusTimer = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
if (!Steamworks.SteamNetworking.IsP2PPacketAvailable()) { break; }
|
||||
|
||||
var packet = Steamworks.SteamNetworking.ReadP2PPacket();
|
||||
if (packet is { SteamId: var steamId, Data: var data })
|
||||
{
|
||||
OnP2PData(steamId, data, data.Length);
|
||||
if (!isActive) { return; }
|
||||
receivedBytes += data.Length;
|
||||
}
|
||||
}
|
||||
socket?.ProcessIncomingMessages();
|
||||
|
||||
GameMain.Client?.NetStats?.AddValue(NetStats.NetStatType.ReceivedBytes, receivedBytes);
|
||||
GameMain.Client?.NetStats?.AddValue(NetStats.NetStatType.SentBytes, sentBytes);
|
||||
@@ -258,8 +237,7 @@ namespace Barotrauma.Networking
|
||||
analyticsTag: "NoContentPackages");
|
||||
return;
|
||||
}
|
||||
callbacks.OnInitializationComplete.Invoke();
|
||||
initializationStep = ConnectionInitialization.Success;
|
||||
OnInitializationComplete();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -287,6 +265,21 @@ namespace Barotrauma.Networking
|
||||
if (!isActive) { return; }
|
||||
|
||||
byte[] bufAux = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _);
|
||||
if (bufAux.Length > MessageFragment.MaxSize)
|
||||
{
|
||||
var fragments = fragmenter.FragmentMessage(msg.Buffer.AsSpan()[..msg.LengthBytes]);
|
||||
foreach (var fragment in fragments)
|
||||
{
|
||||
var fragmentHeaders = new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = DeliveryMethod.Reliable,
|
||||
PacketHeader = PacketHeader.IsDataFragment,
|
||||
Initialization = null
|
||||
};
|
||||
SendMsgInternal(fragmentHeaders, fragment);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var headers = new PeerPacketHeaders
|
||||
{
|
||||
@@ -346,8 +339,6 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
SteamManager.LeaveLobby();
|
||||
|
||||
isActive = false;
|
||||
|
||||
var headers = new PeerPacketHeaders
|
||||
@@ -360,11 +351,9 @@ namespace Barotrauma.Networking
|
||||
|
||||
Thread.Sleep(100);
|
||||
|
||||
Steamworks.SteamNetworking.ResetActions();
|
||||
Steamworks.SteamNetworking.CloseP2PSessionWithUser(hostSteamId.Value);
|
||||
|
||||
if (steamAuthTicket.TryUnwrap(out var ticket)) { ticket.Cancel(); }
|
||||
steamAuthTicket = Option.None;
|
||||
socket?.CloseConnection(ServerEndpoint);
|
||||
socket?.Dispose();
|
||||
socket = null;
|
||||
|
||||
callbacks.OnDisconnect.Invoke(peerDisconnectPacket);
|
||||
}
|
||||
@@ -374,33 +363,62 @@ namespace Barotrauma.Networking
|
||||
IWriteMessage msgToSend = new WriteOnlyMessage();
|
||||
msgToSend.WriteNetSerializableStruct(headers);
|
||||
body?.Write(msgToSend);
|
||||
ForwardToSteamP2P(msgToSend, headers.DeliveryMethod);
|
||||
ForwardToRemotePeer(msgToSend, headers.DeliveryMethod);
|
||||
}
|
||||
|
||||
private void ForwardToSteamP2P(IWriteMessage msg, DeliveryMethod deliveryMethod)
|
||||
private void ForwardToRemotePeer(IWriteMessage msg, DeliveryMethod deliveryMethod)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
if (socket is null) { return; }
|
||||
|
||||
heartbeatTimer = 5.0;
|
||||
|
||||
int length = msg.LengthBytes;
|
||||
|
||||
bool successSend = Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, msg.Buffer, length, 0, deliveryMethod.ToSteam());
|
||||
if (length + 4 >= MsgConstants.MTU)
|
||||
{
|
||||
DebugConsole.Log($"WARNING: message length comes close to exceeding MTU, forcing reliable send ({length} bytes)");
|
||||
deliveryMethod = DeliveryMethod.Reliable;
|
||||
}
|
||||
|
||||
bool success = socket.SendMessage(ServerEndpoint, msg, deliveryMethod);
|
||||
|
||||
sentBytes += length;
|
||||
|
||||
if (successSend) { return; }
|
||||
if (success) { return; }
|
||||
|
||||
if (deliveryMethod is DeliveryMethod.Unreliable)
|
||||
{
|
||||
DebugConsole.Log($"WARNING: message couldn't be sent unreliably, forcing reliable send ({length} bytes)");
|
||||
successSend = Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, msg.Buffer, length, 0, DeliveryMethod.Reliable.ToSteam());
|
||||
success = socket.SendMessage(ServerEndpoint, msg, DeliveryMethod.Reliable);
|
||||
sentBytes += length;
|
||||
}
|
||||
|
||||
if (!successSend)
|
||||
if (!success)
|
||||
{
|
||||
DebugConsole.AddWarning($"Failed to send message to remote peer! ({length} bytes)");
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task<Option<AccountId>> GetAccountId()
|
||||
{
|
||||
if (SteamManager.IsInitialized) { return SteamManager.GetSteamId().Select(id => (AccountId)id); }
|
||||
|
||||
if (EosInterface.IdQueries.GetLoggedInPuids() is not { Length: > 0 } puids)
|
||||
{
|
||||
return Option.None;
|
||||
}
|
||||
var externalAccountIdsResult = await EosInterface.IdQueries.GetSelfExternalAccountIds(puids[0]);
|
||||
if (!externalAccountIdsResult.TryUnwrapSuccess(out var externalAccountIds)
|
||||
|| externalAccountIds is not { Length: > 0 })
|
||||
{
|
||||
return Option.None;
|
||||
}
|
||||
return Option.Some(externalAccountIds[0]);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
public override void ForceTimeOut()
|
||||
{
|
||||
timeout = 0.0f;
|
||||
@@ -0,0 +1,557 @@
|
||||
#nullable enable
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Steam;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
sealed class P2POwnerPeer : ClientPeer<PipeEndpoint>
|
||||
{
|
||||
private P2PSocket? socket;
|
||||
private readonly ImmutableDictionary<AuthenticationTicketKind, Authenticator> authenticators;
|
||||
|
||||
private readonly P2PEndpoint selfPrimaryEndpoint;
|
||||
private AccountInfo selfAccountInfo;
|
||||
|
||||
private long sentBytes, receivedBytes;
|
||||
|
||||
private sealed class RemotePeer
|
||||
{
|
||||
public enum AuthenticationStatus
|
||||
{
|
||||
NotAuthenticated,
|
||||
AuthenticationPending,
|
||||
SuccessfullyAuthenticated
|
||||
}
|
||||
|
||||
public readonly P2PEndpoint Endpoint;
|
||||
public AccountInfo AccountInfo;
|
||||
|
||||
public readonly record struct DisconnectInfo(
|
||||
double TimeToGiveUp,
|
||||
PeerDisconnectPacket Packet);
|
||||
|
||||
public Option<DisconnectInfo> PendingDisconnect;
|
||||
public AuthenticationStatus AuthStatus;
|
||||
|
||||
public readonly record struct UnauthedMessage(byte[] Bytes, int LengthBytes);
|
||||
|
||||
public readonly List<UnauthedMessage> UnauthedMessages;
|
||||
|
||||
public RemotePeer(P2PEndpoint endpoint)
|
||||
{
|
||||
Endpoint = endpoint;
|
||||
AccountInfo = AccountInfo.None;
|
||||
PendingDisconnect = Option.None;
|
||||
AuthStatus = AuthenticationStatus.NotAuthenticated;
|
||||
|
||||
UnauthedMessages = new List<UnauthedMessage>();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<RemotePeer> remotePeers = new();
|
||||
|
||||
public P2POwnerPeer(Callbacks callbacks, int ownerKey, ImmutableArray<P2PEndpoint> allEndpoints) :
|
||||
base(new PipeEndpoint(), allEndpoints.Cast<Endpoint>().ToImmutableArray(), callbacks, Option<int>.Some(ownerKey))
|
||||
{
|
||||
ServerConnection = null;
|
||||
|
||||
isActive = false;
|
||||
|
||||
var selfSteamEndpoint = allEndpoints.FirstOrNone(e => e is SteamP2PEndpoint);
|
||||
var selfEosEndpoint = allEndpoints.FirstOrNone(e => e is EosP2PEndpoint);
|
||||
var selfPrimaryEndpointOption = selfSteamEndpoint.Fallback(selfEosEndpoint);
|
||||
if (!selfPrimaryEndpointOption.TryUnwrap(out var selfPrimaryEndpointNotNull))
|
||||
{
|
||||
throw new Exception("Could not determine endpoint for P2POwnerPeer");
|
||||
}
|
||||
selfPrimaryEndpoint = selfPrimaryEndpointNotNull;
|
||||
selfAccountInfo = AccountInfo.None;
|
||||
authenticators = Authenticator.GetAuthenticatorsForHost(Option.Some<Endpoint>(selfPrimaryEndpoint));
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
if (isActive) { return; }
|
||||
|
||||
initializationStep = ConnectionInitialization.AuthInfoAndVersion;
|
||||
|
||||
ServerConnection = new PipeConnection(Option.None)
|
||||
{
|
||||
Status = NetworkConnectionStatus.Connected
|
||||
};
|
||||
|
||||
remotePeers.Clear();
|
||||
|
||||
var socketCallbacks = new P2PSocket.Callbacks(OnIncomingConnection, OnConnectionClosed, OnP2PData);
|
||||
var socketCreateResult = DualStackP2PSocket.Create(socketCallbacks);
|
||||
socket = socketCreateResult.TryUnwrapSuccess(out var s)
|
||||
? s
|
||||
: throw new Exception($"Failed to create dual-stack socket: {socketCreateResult}");
|
||||
|
||||
TaskPool.Add("P2POwnerPeer.GetAccountId", GetAccountId(), t =>
|
||||
{
|
||||
if (t.TryGetResult(out Option<AccountId> accountIdOption) && accountIdOption.TryUnwrap(out var accountId))
|
||||
{
|
||||
selfAccountInfo = new AccountInfo(accountId);
|
||||
}
|
||||
|
||||
if (selfAccountInfo.IsNone)
|
||||
{
|
||||
Close(PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationFailed));
|
||||
}
|
||||
});
|
||||
|
||||
isActive = true;
|
||||
}
|
||||
|
||||
private bool OnIncomingConnection(P2PEndpoint remoteEndpoint)
|
||||
{
|
||||
if (!isActive) { return false; }
|
||||
|
||||
if (remotePeers.None(p => p.Endpoint == remoteEndpoint))
|
||||
{
|
||||
remotePeers.Add(new RemotePeer(remoteEndpoint));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnConnectionClosed(P2PEndpoint remoteEndpoint, PeerDisconnectPacket disconnectPacket)
|
||||
{
|
||||
var remotePeer
|
||||
= remotePeers.Find(p => p.Endpoint == remoteEndpoint);
|
||||
if (remotePeer is null) { return; }
|
||||
CommunicatePeerDisconnectToServerProcess(
|
||||
remotePeer,
|
||||
remotePeer.PendingDisconnect.Select(d => d.Packet).Fallback(disconnectPacket));
|
||||
}
|
||||
|
||||
private void OnP2PData(P2PEndpoint senderEndpoint, IReadMessage inc)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
receivedBytes += inc.LengthBytes;
|
||||
|
||||
var remotePeer = remotePeers.Find(p => p.Endpoint == senderEndpoint);
|
||||
if (remotePeer is null) { return; }
|
||||
if (remotePeer.PendingDisconnect.IsSome()) { return; }
|
||||
|
||||
var peerPacketHeaders = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
|
||||
|
||||
PacketHeader packetHeader = peerPacketHeaders.PacketHeader;
|
||||
|
||||
if (packetHeader.IsConnectionInitializationStep())
|
||||
{
|
||||
ConnectionInitialization initialization = peerPacketHeaders.Initialization ?? throw new Exception("Initialization step missing");
|
||||
if (initialization == ConnectionInitialization.AuthInfoAndVersion
|
||||
&& remotePeer.AuthStatus == RemotePeer.AuthenticationStatus.NotAuthenticated)
|
||||
{
|
||||
StartAuthTask(inc, remotePeer);
|
||||
}
|
||||
}
|
||||
|
||||
if (remotePeer.AuthStatus == RemotePeer.AuthenticationStatus.AuthenticationPending)
|
||||
{
|
||||
remotePeer.UnauthedMessages.Add(new RemotePeer.UnauthedMessage(inc.Buffer, inc.LengthBytes));
|
||||
}
|
||||
else
|
||||
{
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
outMsg.WriteNetSerializableStruct(new P2POwnerToServerHeader
|
||||
{
|
||||
EndpointStr = remotePeer.Endpoint.StringRepresentation,
|
||||
AccountInfo = remotePeer.AccountInfo
|
||||
});
|
||||
outMsg.WriteBytes(inc.Buffer, 0, inc.LengthBytes);
|
||||
|
||||
ForwardToServerProcess(outMsg);
|
||||
}
|
||||
}
|
||||
|
||||
private void StartAuthTask(IReadMessage inc, RemotePeer remotePeer)
|
||||
{
|
||||
remotePeer.AuthStatus = RemotePeer.AuthenticationStatus.AuthenticationPending;
|
||||
|
||||
var packet = INetSerializableStruct.Read<ClientAuthTicketAndVersionPacket>(inc);
|
||||
|
||||
void failAuth()
|
||||
{
|
||||
CommunicateDisconnectToRemotePeer(remotePeer, PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationFailed));
|
||||
}
|
||||
|
||||
if (!packet.AuthTicket.TryUnwrap(out var authenticationTicket))
|
||||
{
|
||||
failAuth();
|
||||
return;
|
||||
}
|
||||
if (!authenticators.TryGetValue(authenticationTicket.Kind, out var authenticator))
|
||||
{
|
||||
failAuth();
|
||||
return;
|
||||
}
|
||||
TaskPool.Add($"P2POwnerPeer.VerifyRemotePeerAccountId",
|
||||
authenticator.VerifyTicket(authenticationTicket),
|
||||
t =>
|
||||
{
|
||||
if (!t.TryGetResult(out AccountInfo accountInfo)
|
||||
|| accountInfo.IsNone)
|
||||
{
|
||||
failAuth();
|
||||
return;
|
||||
}
|
||||
|
||||
remotePeer.AccountInfo = accountInfo;
|
||||
remotePeer.AuthStatus = RemotePeer.AuthenticationStatus.SuccessfullyAuthenticated;
|
||||
foreach (var unauthedMessage in remotePeer.UnauthedMessages)
|
||||
{
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.WriteNetSerializableStruct(new P2POwnerToServerHeader
|
||||
{
|
||||
EndpointStr = remotePeer.Endpoint.StringRepresentation,
|
||||
AccountInfo = accountInfo
|
||||
});
|
||||
msg.WriteBytes(unauthedMessage.Bytes, 0, unauthedMessage.LengthBytes);
|
||||
ForwardToServerProcess(msg);
|
||||
}
|
||||
remotePeer.UnauthedMessages.Clear();
|
||||
});
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
if (ChildServerRelay.HasShutDown || ChildServerRelay.Process is not { HasExited: false })
|
||||
{
|
||||
Close(PeerDisconnectPacket.WithReason(DisconnectReason.ServerCrashed));
|
||||
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage);
|
||||
msgBox.Buttons[0].OnClicked += (btn, obj) =>
|
||||
{
|
||||
GameMain.MainMenuScreen.Select();
|
||||
return false;
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
if (selfAccountInfo.IsNone) { return; }
|
||||
|
||||
for (int i = remotePeers.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (remotePeers[i].PendingDisconnect.TryUnwrap(out var pendingDisconnect) && pendingDisconnect.TimeToGiveUp < Timing.TotalTime)
|
||||
{
|
||||
CommunicatePeerDisconnectToServerProcess(remotePeers[i], pendingDisconnect.Packet);
|
||||
}
|
||||
}
|
||||
|
||||
socket?.ProcessIncomingMessages();
|
||||
|
||||
GameMain.Client?.NetStats?.AddValue(NetStats.NetStatType.ReceivedBytes, receivedBytes);
|
||||
GameMain.Client?.NetStats?.AddValue(NetStats.NetStatType.SentBytes, sentBytes);
|
||||
|
||||
foreach (var incBuf in ChildServerRelay.Read())
|
||||
{
|
||||
ChildServerRelay.DisposeLocalHandles();
|
||||
IReadMessage inc = new ReadOnlyMessage(incBuf, false, 0, incBuf.Length, ServerConnection);
|
||||
HandleServerMessage(inc);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleServerMessage(IReadMessage inc)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
var recipientInfo = INetSerializableStruct.Read<P2PServerToOwnerHeader>(inc);
|
||||
if (!recipientInfo.Endpoint.TryUnwrap(out var recipientEndpoint)) { return; }
|
||||
var peerPacketHeaders = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
|
||||
|
||||
if (recipientEndpoint != selfPrimaryEndpoint)
|
||||
{
|
||||
HandleMessageForRemotePeer(peerPacketHeaders, recipientEndpoint, inc);
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleMessageForOwner(peerPacketHeaders, inc);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] GetRemainingBytes(IReadMessage msg)
|
||||
{
|
||||
return msg.Buffer[msg.BytePosition..msg.LengthBytes];
|
||||
}
|
||||
|
||||
private void HandleMessageForRemotePeer(PeerPacketHeaders peerPacketHeaders, P2PEndpoint recipientEndpoint, IReadMessage inc)
|
||||
{
|
||||
var (deliveryMethod, packetHeader, initialization) = peerPacketHeaders;
|
||||
|
||||
if (!packetHeader.IsServerMessage())
|
||||
{
|
||||
DebugConsole.ThrowError("Received non-server message meant for remote peer");
|
||||
return;
|
||||
}
|
||||
|
||||
RemotePeer? peer = remotePeers.Find(p => p.Endpoint == recipientEndpoint);
|
||||
if (peer is null) { return; }
|
||||
|
||||
if (packetHeader.IsDisconnectMessage())
|
||||
{
|
||||
var packet = INetSerializableStruct.Read<PeerDisconnectPacket>(inc);
|
||||
CommunicateDisconnectToRemotePeer(peer, packet);
|
||||
return;
|
||||
}
|
||||
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
|
||||
outMsg.WriteNetSerializableStruct(new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = deliveryMethod,
|
||||
PacketHeader = packetHeader,
|
||||
Initialization = initialization
|
||||
});
|
||||
|
||||
if (packetHeader.IsConnectionInitializationStep())
|
||||
{
|
||||
var initRelayPacket = new P2PInitializationRelayPacket
|
||||
{
|
||||
LobbyID = 0,
|
||||
Message = new PeerPacketMessage
|
||||
{
|
||||
Buffer = GetRemainingBytes(inc)
|
||||
}
|
||||
};
|
||||
|
||||
outMsg.WriteNetSerializableStruct(initRelayPacket);
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] userMessage = GetRemainingBytes(inc);
|
||||
outMsg.WriteBytes(userMessage, 0, userMessage.Length);
|
||||
}
|
||||
|
||||
ForwardToRemotePeer(deliveryMethod, recipientEndpoint, outMsg);
|
||||
}
|
||||
|
||||
private void HandleMessageForOwner(PeerPacketHeaders peerPacketHeaders, IReadMessage inc)
|
||||
{
|
||||
var (_, packetHeader, _) = peerPacketHeaders;
|
||||
|
||||
if (packetHeader.IsDisconnectMessage())
|
||||
{
|
||||
DebugConsole.ThrowError("Received disconnect message from owned server");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!packetHeader.IsServerMessage())
|
||||
{
|
||||
DebugConsole.ThrowError("Received non-server message from owned server");
|
||||
return;
|
||||
}
|
||||
|
||||
if (packetHeader.IsHeartbeatMessage())
|
||||
{
|
||||
return; //no timeout since we're using pipes, ignore this message
|
||||
}
|
||||
|
||||
if (packetHeader.IsConnectionInitializationStep())
|
||||
{
|
||||
if (selfAccountInfo.IsNone) { throw new InvalidOperationException($"Cannot initialize {nameof(P2POwnerPeer)} because {nameof(selfAccountInfo)} is not defined"); }
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
outMsg.WriteNetSerializableStruct(new P2POwnerToServerHeader
|
||||
{
|
||||
EndpointStr = selfPrimaryEndpoint.StringRepresentation,
|
||||
AccountInfo = selfAccountInfo
|
||||
});
|
||||
outMsg.WriteNetSerializableStruct(new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = DeliveryMethod.Reliable,
|
||||
PacketHeader = PacketHeader.IsConnectionInitializationStep,
|
||||
Initialization = ConnectionInitialization.AuthInfoAndVersion
|
||||
});
|
||||
outMsg.WriteNetSerializableStruct(new P2PInitializationOwnerPacket(
|
||||
Name: GameMain.Client.Name,
|
||||
AccountId: selfAccountInfo.AccountId.Fallback(default(AccountId)!)));
|
||||
ForwardToServerProcess(outMsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnInitializationComplete();
|
||||
|
||||
PeerPacketMessage packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
|
||||
IReadMessage msg = new ReadOnlyMessage(packet.Buffer, packetHeader.IsCompressed(), 0, packet.Length, ServerConnection);
|
||||
callbacks.OnMessageReceived.Invoke(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private void CommunicateDisconnectToRemotePeer(RemotePeer peer, PeerDisconnectPacket peerDisconnectPacket)
|
||||
{
|
||||
if (peer.PendingDisconnect.IsNone())
|
||||
{
|
||||
peer.PendingDisconnect = Option.Some(
|
||||
new RemotePeer.DisconnectInfo(
|
||||
Timing.TotalTime + 3f,
|
||||
peerDisconnectPacket));
|
||||
}
|
||||
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
outMsg.WriteNetSerializableStruct(new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = DeliveryMethod.Reliable,
|
||||
PacketHeader = PacketHeader.IsServerMessage | PacketHeader.IsDisconnectMessage
|
||||
});
|
||||
outMsg.WriteNetSerializableStruct(peerDisconnectPacket);
|
||||
|
||||
ForwardToRemotePeer(DeliveryMethod.Reliable, peer.Endpoint, outMsg);
|
||||
}
|
||||
|
||||
private void CommunicatePeerDisconnectToServerProcess(RemotePeer peer, PeerDisconnectPacket peerDisconnectPacket)
|
||||
{
|
||||
if (!remotePeers.Remove(peer)) { return; }
|
||||
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
outMsg.WriteNetSerializableStruct(new P2POwnerToServerHeader
|
||||
{
|
||||
EndpointStr = peer.Endpoint.StringRepresentation,
|
||||
AccountInfo = peer.AccountInfo
|
||||
});
|
||||
outMsg.WriteNetSerializableStruct(new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = DeliveryMethod.Reliable,
|
||||
PacketHeader = PacketHeader.IsDisconnectMessage
|
||||
});
|
||||
outMsg.WriteNetSerializableStruct(peerDisconnectPacket);
|
||||
if (peer.AccountInfo.AccountId.TryUnwrap(out var accountId))
|
||||
{
|
||||
authenticators.Values.ForEach(authenticator => authenticator.EndAuthSession(accountId));
|
||||
}
|
||||
|
||||
ForwardToServerProcess(outMsg);
|
||||
|
||||
socket?.CloseConnection(peer.Endpoint);
|
||||
}
|
||||
|
||||
public override void SendPassword(string password)
|
||||
{
|
||||
//owner doesn't send passwords
|
||||
}
|
||||
|
||||
public override void Close(PeerDisconnectPacket peerDisconnectPacket)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
isActive = false;
|
||||
|
||||
for (int i = remotePeers.Count - 1; i >= 0; i--)
|
||||
{
|
||||
CommunicateDisconnectToRemotePeer(remotePeers[i], peerDisconnectPacket);
|
||||
}
|
||||
|
||||
Thread.Sleep(100);
|
||||
|
||||
for (int i = remotePeers.Count - 1; i >= 0; i--)
|
||||
{
|
||||
CommunicatePeerDisconnectToServerProcess(remotePeers[i], peerDisconnectPacket);
|
||||
}
|
||||
|
||||
socket?.Dispose();
|
||||
socket = null;
|
||||
|
||||
callbacks.OnDisconnect.Invoke(peerDisconnectPacket);
|
||||
}
|
||||
|
||||
public override void Send(IWriteMessage msg, DeliveryMethod deliveryMethod, bool compressPastThreshold = true)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
IWriteMessage msgToSend = new WriteOnlyMessage();
|
||||
byte[] msgData = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _);
|
||||
msgToSend.WriteNetSerializableStruct(new P2POwnerToServerHeader
|
||||
{
|
||||
EndpointStr = selfPrimaryEndpoint.StringRepresentation,
|
||||
AccountInfo = selfAccountInfo
|
||||
});
|
||||
msgToSend.WriteNetSerializableStruct(new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = deliveryMethod,
|
||||
PacketHeader = isCompressed ? PacketHeader.IsCompressed : PacketHeader.None
|
||||
});
|
||||
msgToSend.WriteNetSerializableStruct(new PeerPacketMessage
|
||||
{
|
||||
Buffer = msgData
|
||||
});
|
||||
ForwardToServerProcess(msgToSend);
|
||||
}
|
||||
|
||||
protected override void SendMsgInternal(PeerPacketHeaders headers, INetSerializableStruct? body)
|
||||
{
|
||||
//not currently used by P2POwnerPeer
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private static void ForwardToServerProcess(IWriteMessage msg)
|
||||
{
|
||||
byte[] bufToSend = new byte[msg.LengthBytes];
|
||||
msg.Buffer[..msg.LengthBytes].CopyTo(bufToSend.AsSpan());
|
||||
ChildServerRelay.Write(bufToSend);
|
||||
}
|
||||
|
||||
private void ForwardToRemotePeer(DeliveryMethod deliveryMethod, P2PEndpoint recipient, IWriteMessage outMsg)
|
||||
{
|
||||
if (socket is null) { return; }
|
||||
|
||||
int length = outMsg.LengthBytes;
|
||||
|
||||
if (length + 4 >= MsgConstants.MTU)
|
||||
{
|
||||
DebugConsole.Log($"WARNING: message length comes close to exceeding MTU, forcing reliable send ({length} bytes)");
|
||||
deliveryMethod = DeliveryMethod.Reliable;
|
||||
}
|
||||
|
||||
var success = socket.SendMessage(recipient, outMsg, deliveryMethod);
|
||||
|
||||
sentBytes += length;
|
||||
|
||||
if (success) { return; }
|
||||
|
||||
if (deliveryMethod is DeliveryMethod.Unreliable)
|
||||
{
|
||||
DebugConsole.Log($"WARNING: message couldn't be sent unreliably, forcing reliable send ({length} bytes)");
|
||||
success = socket.SendMessage(recipient, outMsg, DeliveryMethod.Reliable);
|
||||
sentBytes += length;
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
DebugConsole.AddWarning($"Failed to send message to remote peer! ({length} bytes)");
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task<Option<AccountId>> GetAccountId()
|
||||
{
|
||||
if (SteamManager.IsInitialized) { return SteamManager.GetSteamId().Select(id => (AccountId)id); }
|
||||
|
||||
if (EosInterface.IdQueries.GetLoggedInPuids() is not { Length: > 0 } puids)
|
||||
{
|
||||
return Option.None;
|
||||
}
|
||||
var externalAccountIdsResult = await EosInterface.IdQueries.GetSelfExternalAccountIds(puids[0]);
|
||||
if (!externalAccountIdsResult.TryUnwrapSuccess(out var externalAccountIds)
|
||||
|| externalAccountIds is not { Length: > 0 })
|
||||
{
|
||||
return Option.None;
|
||||
}
|
||||
return Option.Some(externalAccountIds[0]);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
public override void ForceTimeOut()
|
||||
{
|
||||
//TODO: reimplement?
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
-492
@@ -1,492 +0,0 @@
|
||||
#nullable enable
|
||||
using Barotrauma.Steam;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
sealed class SteamP2POwnerPeer : ClientPeer
|
||||
{
|
||||
private readonly SteamId selfSteamID;
|
||||
private UInt64 ownerKey64 => unchecked((UInt64)ownerKey.Fallback(0));
|
||||
|
||||
private SteamId ReadSteamId(IReadMessage inc) => new SteamId(inc.ReadUInt64() ^ ownerKey64);
|
||||
private void WriteSteamId(IWriteMessage msg, SteamId val) => msg.WriteUInt64(val.Value ^ ownerKey64);
|
||||
|
||||
private long sentBytes, receivedBytes;
|
||||
|
||||
private sealed class RemotePeer
|
||||
{
|
||||
public readonly SteamId SteamId;
|
||||
public Option<SteamId> OwnerSteamId;
|
||||
public double? DisconnectTime;
|
||||
public bool Authenticating;
|
||||
public bool Authenticated;
|
||||
|
||||
public readonly struct UnauthedMessage
|
||||
{
|
||||
public readonly SteamId Sender;
|
||||
public readonly byte[] Bytes;
|
||||
public readonly int Length;
|
||||
|
||||
public UnauthedMessage(SteamId sender, byte[] bytes)
|
||||
{
|
||||
Sender = sender;
|
||||
Bytes = bytes;
|
||||
Length = bytes.Length;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly List<UnauthedMessage> UnauthedMessages;
|
||||
|
||||
public RemotePeer(SteamId steamId)
|
||||
{
|
||||
SteamId = steamId;
|
||||
OwnerSteamId = Option<SteamId>.None();
|
||||
DisconnectTime = null;
|
||||
Authenticating = false;
|
||||
Authenticated = false;
|
||||
|
||||
UnauthedMessages = new List<UnauthedMessage>();
|
||||
}
|
||||
}
|
||||
|
||||
private List<RemotePeer> remotePeers = null!;
|
||||
|
||||
public SteamP2POwnerPeer(Callbacks callbacks, int ownerKey) : base(new PipeEndpoint(), callbacks, Option<int>.Some(ownerKey))
|
||||
{
|
||||
ServerConnection = null;
|
||||
|
||||
isActive = false;
|
||||
|
||||
selfSteamID = SteamManager.GetSteamId().TryUnwrap(out var steamId)
|
||||
? steamId
|
||||
: throw new InvalidOperationException("Steamworks not initialized");
|
||||
}
|
||||
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
if (isActive) { return; }
|
||||
|
||||
initializationStep = ConnectionInitialization.SteamTicketAndVersion;
|
||||
|
||||
ServerConnection = new PipeConnection(selfSteamID)
|
||||
{
|
||||
Status = NetworkConnectionStatus.Connected
|
||||
};
|
||||
|
||||
remotePeers = new List<RemotePeer>();
|
||||
|
||||
Steamworks.SteamNetworking.ResetActions();
|
||||
Steamworks.SteamNetworking.OnP2PSessionRequest = OnIncomingConnection;
|
||||
Steamworks.SteamUser.OnValidateAuthTicketResponse += OnAuthChange;
|
||||
|
||||
Steamworks.SteamNetworking.AllowP2PPacketRelay(true);
|
||||
|
||||
isActive = true;
|
||||
}
|
||||
|
||||
private void OnAuthChange(Steamworks.SteamId steamId, Steamworks.SteamId ownerId, Steamworks.AuthResponse status)
|
||||
{
|
||||
RemotePeer? remotePeer = remotePeers.Find(p => p.SteamId.Value == steamId);
|
||||
|
||||
if (remotePeer == null) { return; }
|
||||
|
||||
if (status == Steamworks.AuthResponse.OK)
|
||||
{
|
||||
if (remotePeer.Authenticated) { return; }
|
||||
|
||||
SteamId ownerSteamId = new SteamId(ownerId);
|
||||
remotePeer.OwnerSteamId = Option<SteamId>.Some(ownerSteamId);
|
||||
remotePeer.Authenticated = true;
|
||||
remotePeer.Authenticating = false;
|
||||
foreach (var unauthedMessage in remotePeer.UnauthedMessages)
|
||||
{
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
WriteSteamId(msg, unauthedMessage.Sender);
|
||||
WriteSteamId(msg, ownerSteamId);
|
||||
msg.WriteBytes(unauthedMessage.Bytes, 0, unauthedMessage.Length);
|
||||
ForwardToServerProcess(msg);
|
||||
}
|
||||
|
||||
remotePeer.UnauthedMessages.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
DisconnectPeer(remotePeer, PeerDisconnectPacket.SteamAuthError(status));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnIncomingConnection(Steamworks.SteamId steamId)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
if (remotePeers.None(p => p.SteamId.Value == steamId))
|
||||
{
|
||||
remotePeers.Add(new RemotePeer(new SteamId(steamId)));
|
||||
}
|
||||
|
||||
Steamworks.SteamNetworking.AcceptP2PSessionWithUser(steamId); //accept all connections, the server will figure things out later
|
||||
}
|
||||
|
||||
private void OnP2PData(ulong steamId, IReadMessage inc)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
RemotePeer? remotePeer = remotePeers.Find(p => p.SteamId.Value == steamId);
|
||||
if (remotePeer == null) { return; }
|
||||
|
||||
if (remotePeer.DisconnectTime != null) { return; }
|
||||
|
||||
try
|
||||
{
|
||||
ProcessP2PData(steamId, remotePeer, inc);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
string errorMsg = $"Server failed to read an incoming P2P message. {{{e}}}\n{e.StackTrace.CleanupStackTrace()}";
|
||||
GameAnalyticsManager.AddErrorEventOnce($"SteamP2POwnerPeer.OnP2PData:OwnerReadException{e.TargetSite}", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
#else
|
||||
if (GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.ThrowError(errorMsg); }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessP2PData(ulong steamId, RemotePeer remotePeer, IReadMessage inc)
|
||||
{
|
||||
var (deliveryMethod, packetHeader, connectionInitialization) = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
|
||||
|
||||
if (remotePeer is { Authenticated: false, Authenticating: false } && packetHeader.IsConnectionInitializationStep())
|
||||
{
|
||||
remotePeer.DisconnectTime = null;
|
||||
|
||||
ConnectionInitialization initialization = connectionInitialization ?? throw new Exception("Initialization step missing");
|
||||
if (initialization == ConnectionInitialization.SteamTicketAndVersion)
|
||||
{
|
||||
remotePeer.Authenticating = true;
|
||||
|
||||
var packet = INetSerializableStruct.Read<ClientSteamTicketAndVersionPacket>(inc);
|
||||
|
||||
packet.SteamAuthTicket.TryUnwrap(out var ticket);
|
||||
|
||||
Steamworks.BeginAuthResult authSessionStartState = SteamManager.StartAuthSession(ticket, steamId);
|
||||
if (authSessionStartState != Steamworks.BeginAuthResult.OK)
|
||||
{
|
||||
DisconnectPeer(remotePeer, PeerDisconnectPacket.SteamAuthError(authSessionStartState));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var steamUserId = new SteamId(steamId);
|
||||
if (remotePeer.Authenticating)
|
||||
{
|
||||
remotePeer.UnauthedMessages.Add(new RemotePeer.UnauthedMessage(steamUserId, inc.Buffer));
|
||||
}
|
||||
else
|
||||
{
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
WriteSteamId(outMsg, steamUserId);
|
||||
WriteSteamId(outMsg, remotePeer.OwnerSteamId.Fallback(steamUserId));
|
||||
outMsg.WriteBytes(inc.Buffer, 0, inc.LengthBytes);
|
||||
|
||||
ForwardToServerProcess(outMsg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
if (ChildServerRelay.HasShutDown || !ChildServerRelay.IsProcessAlive)
|
||||
{
|
||||
var gameClient = GameMain.Client;
|
||||
Close(PeerDisconnectPacket.WithReason(DisconnectReason.ServerCrashed));
|
||||
gameClient?.CreateServerCrashMessage();
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = remotePeers.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (remotePeers[i].DisconnectTime != null && remotePeers[i].DisconnectTime < Timing.TotalTime)
|
||||
{
|
||||
ClosePeerSession(remotePeers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
if (!Steamworks.SteamNetworking.IsP2PPacketAvailable()) { break; }
|
||||
|
||||
var packet = Steamworks.SteamNetworking.ReadP2PPacket();
|
||||
if (packet is { SteamId: var steamId, Data: var data })
|
||||
{
|
||||
OnP2PData(steamId, new ReadWriteMessage(data, 0, data.Length * 8, false));
|
||||
receivedBytes += data.Length;
|
||||
}
|
||||
}
|
||||
|
||||
GameMain.Client?.NetStats?.AddValue(NetStats.NetStatType.ReceivedBytes, receivedBytes);
|
||||
GameMain.Client?.NetStats?.AddValue(NetStats.NetStatType.SentBytes, sentBytes);
|
||||
|
||||
while (ChildServerRelay.Read(out byte[] incBuf))
|
||||
{
|
||||
ChildServerRelay.DisposeLocalHandles();
|
||||
IReadMessage inc = new ReadOnlyMessage(incBuf, false, 0, incBuf.Length, ServerConnection);
|
||||
HandleDataMessage(inc);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleDataMessage(IReadMessage inc)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
SteamId recipientSteamId = ReadSteamId(inc);
|
||||
|
||||
var peerPacketHeaders = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
|
||||
|
||||
if (recipientSteamId != selfSteamID)
|
||||
{
|
||||
HandleMessageForRemotePeer(peerPacketHeaders, recipientSteamId, inc);
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleMessageForOwner(peerPacketHeaders, inc);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] GetRemainingBytes(IReadMessage msg)
|
||||
{
|
||||
return msg.Buffer[msg.BytePosition..msg.LengthBytes];
|
||||
}
|
||||
|
||||
private void HandleMessageForRemotePeer(PeerPacketHeaders peerPacketHeaders, SteamId recipientSteamId, IReadMessage inc)
|
||||
{
|
||||
var (deliveryMethod, packetHeader, initialization) = peerPacketHeaders;
|
||||
|
||||
if (!packetHeader.IsServerMessage())
|
||||
{
|
||||
DebugConsole.ThrowError("Received non-server message meant for remote peer");
|
||||
return;
|
||||
}
|
||||
|
||||
RemotePeer? peer = remotePeers.Find(p => p.SteamId == recipientSteamId);
|
||||
if (peer is null) { return; }
|
||||
|
||||
if (packetHeader.IsDisconnectMessage())
|
||||
{
|
||||
var packet = INetSerializableStruct.Read<PeerDisconnectPacket>(inc);
|
||||
DisconnectPeer(peer, packet);
|
||||
return;
|
||||
}
|
||||
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
|
||||
outMsg.WriteNetSerializableStruct(new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = deliveryMethod,
|
||||
PacketHeader = packetHeader,
|
||||
Initialization = initialization
|
||||
});
|
||||
|
||||
if (packetHeader.IsConnectionInitializationStep())
|
||||
{
|
||||
var initRelayPacket = new SteamP2PInitializationRelayPacket
|
||||
{
|
||||
LobbyID = SteamManager.CurrentLobbyID,
|
||||
Message = new PeerPacketMessage
|
||||
{
|
||||
Buffer = GetRemainingBytes(inc)
|
||||
}
|
||||
};
|
||||
|
||||
outMsg.WriteNetSerializableStruct(initRelayPacket);
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] userMessage = GetRemainingBytes(inc);
|
||||
outMsg.WriteBytes(userMessage, 0, userMessage.Length);
|
||||
}
|
||||
|
||||
ForwardToRemotePeer(deliveryMethod, recipientSteamId, outMsg);
|
||||
}
|
||||
|
||||
private void HandleMessageForOwner(PeerPacketHeaders peerPacketHeaders, IReadMessage inc)
|
||||
{
|
||||
var (_, packetHeader, _) = peerPacketHeaders;
|
||||
|
||||
if (packetHeader.IsDisconnectMessage())
|
||||
{
|
||||
DebugConsole.ThrowError("Received disconnect message from owned server");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!packetHeader.IsServerMessage())
|
||||
{
|
||||
DebugConsole.ThrowError("Received non-server message from owned server");
|
||||
return;
|
||||
}
|
||||
|
||||
if (packetHeader.IsHeartbeatMessage())
|
||||
{
|
||||
return; //no timeout since we're using pipes, ignore this message
|
||||
}
|
||||
|
||||
if (packetHeader.IsConnectionInitializationStep())
|
||||
{
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
WriteSteamId(outMsg, selfSteamID);
|
||||
WriteSteamId(outMsg, selfSteamID);
|
||||
outMsg.WriteNetSerializableStruct(new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = DeliveryMethod.Reliable,
|
||||
PacketHeader = PacketHeader.IsConnectionInitializationStep,
|
||||
Initialization = ConnectionInitialization.SteamTicketAndVersion
|
||||
});
|
||||
outMsg.WriteNetSerializableStruct(new SteamP2PInitializationOwnerPacket
|
||||
{
|
||||
OwnerName = GameMain.Client.Name
|
||||
});
|
||||
ForwardToServerProcess(outMsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (initializationStep != ConnectionInitialization.Success)
|
||||
{
|
||||
callbacks.OnInitializationComplete.Invoke();
|
||||
initializationStep = ConnectionInitialization.Success;
|
||||
}
|
||||
|
||||
PeerPacketMessage packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
|
||||
IReadMessage msg = new ReadOnlyMessage(packet.Buffer, packetHeader.IsCompressed(), 0, packet.Length, ServerConnection);
|
||||
callbacks.OnMessageReceived.Invoke(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private void DisconnectPeer(RemotePeer peer, PeerDisconnectPacket peerDisconnectPacket)
|
||||
{
|
||||
peer.DisconnectTime ??= Timing.TotalTime + 1.0;
|
||||
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
outMsg.WriteNetSerializableStruct(new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = DeliveryMethod.Reliable,
|
||||
PacketHeader = PacketHeader.IsServerMessage | PacketHeader.IsDisconnectMessage
|
||||
});
|
||||
outMsg.WriteNetSerializableStruct(peerDisconnectPacket);
|
||||
|
||||
Steamworks.SteamNetworking.SendP2PPacket(peer.SteamId.Value, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable);
|
||||
sentBytes += outMsg.LengthBytes;
|
||||
}
|
||||
|
||||
private void ClosePeerSession(RemotePeer peer)
|
||||
{
|
||||
Steamworks.SteamNetworking.CloseP2PSessionWithUser(peer.SteamId.Value);
|
||||
remotePeers.Remove(peer);
|
||||
}
|
||||
|
||||
public override void SendPassword(string password)
|
||||
{
|
||||
//owner doesn't send passwords
|
||||
}
|
||||
|
||||
public override void Close(PeerDisconnectPacket peerDisconnectPacket)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
isActive = false;
|
||||
|
||||
for (int i = remotePeers.Count - 1; i >= 0; i--)
|
||||
{
|
||||
DisconnectPeer(remotePeers[i], PeerDisconnectPacket.WithReason(DisconnectReason.ServerShutdown));
|
||||
}
|
||||
|
||||
Thread.Sleep(100);
|
||||
|
||||
for (int i = remotePeers.Count - 1; i >= 0; i--)
|
||||
{
|
||||
ClosePeerSession(remotePeers[i]);
|
||||
}
|
||||
|
||||
callbacks.OnDisconnect.Invoke(peerDisconnectPacket);
|
||||
|
||||
SteamManager.LeaveLobby();
|
||||
Steamworks.SteamNetworking.ResetActions();
|
||||
Steamworks.SteamUser.OnValidateAuthTicketResponse -= OnAuthChange;
|
||||
}
|
||||
|
||||
public override void Send(IWriteMessage msg, DeliveryMethod deliveryMethod, bool compressPastThreshold = true)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
IWriteMessage msgToSend = new WriteOnlyMessage();
|
||||
byte[] msgData = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _);
|
||||
WriteSteamId(msgToSend, selfSteamID);
|
||||
WriteSteamId(msgToSend, selfSteamID);
|
||||
msgToSend.WriteNetSerializableStruct(new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = deliveryMethod,
|
||||
PacketHeader = isCompressed ? PacketHeader.IsCompressed : PacketHeader.None
|
||||
});
|
||||
msgToSend.WriteNetSerializableStruct(new PeerPacketMessage
|
||||
{
|
||||
Buffer = msgData
|
||||
});
|
||||
ForwardToServerProcess(msgToSend);
|
||||
}
|
||||
|
||||
protected override void SendMsgInternal(PeerPacketHeaders headers, INetSerializableStruct? body)
|
||||
{
|
||||
//not currently used by SteamP2POwnerPeer
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private static void ForwardToServerProcess(IWriteMessage msg)
|
||||
{
|
||||
byte[] bufToSend = new byte[msg.LengthBytes];
|
||||
msg.Buffer[..msg.LengthBytes].CopyTo(bufToSend.AsSpan());
|
||||
ChildServerRelay.Write(bufToSend);
|
||||
}
|
||||
|
||||
private void ForwardToRemotePeer(DeliveryMethod deliveryMethod, SteamId recipent, IWriteMessage outMsg)
|
||||
{
|
||||
byte[] buf = outMsg.PrepareForSending(compressPastThreshold: false, out _, out int length);
|
||||
|
||||
if (length + 4 >= MsgConstants.MTU)
|
||||
{
|
||||
DebugConsole.Log($"WARNING: message length comes close to exceeding MTU, forcing reliable send ({length} bytes)");
|
||||
deliveryMethod = DeliveryMethod.Reliable;
|
||||
}
|
||||
|
||||
bool successSend = Steamworks.SteamNetworking.SendP2PPacket(recipent.Value, buf, length, 0, deliveryMethod.ToSteam());
|
||||
sentBytes += length;
|
||||
|
||||
if (successSend) { return; }
|
||||
|
||||
if (deliveryMethod is DeliveryMethod.Unreliable)
|
||||
{
|
||||
DebugConsole.Log($"WARNING: message couldn't be sent unreliably, forcing reliable send ({length} bytes)");
|
||||
successSend = Steamworks.SteamNetworking.SendP2PPacket(recipent.Value, buf, length, 0, DeliveryMethod.Reliable.ToSteam());
|
||||
sentBytes += length;
|
||||
}
|
||||
|
||||
if (!successSend)
|
||||
{
|
||||
DebugConsole.AddWarning($"Failed to send message to remote peer! ({length} bytes)");
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
public override void ForceTimeOut()
|
||||
{
|
||||
//TODO: reimplement?
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
abstract class FriendProvider
|
||||
{
|
||||
public abstract ServerListScreen.FriendInfo[] RetrieveFriends();
|
||||
public abstract void RetrieveAvatar(ServerListScreen.FriendInfo friend, ServerListScreen.AvatarSize avatarSize);
|
||||
public abstract string GetUserName();
|
||||
}
|
||||
}
|
||||
-67
@@ -1,67 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Steam;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class SteamFriendProvider : FriendProvider
|
||||
{
|
||||
private static ServerListScreen.FriendInfo FromSteamFriend(Steamworks.Friend steamFriend)
|
||||
=> new ServerListScreen.FriendInfo(
|
||||
steamFriend.Name,
|
||||
new SteamId(steamFriend.Id),
|
||||
steamFriend.State switch
|
||||
{
|
||||
Steamworks.FriendState.Offline => ServerListScreen.FriendInfo.Status.Offline,
|
||||
Steamworks.FriendState.Invisible => ServerListScreen.FriendInfo.Status.Offline,
|
||||
_ when steamFriend.IsPlayingThisGame => ServerListScreen.FriendInfo.Status.PlayingBarotrauma,
|
||||
_ when steamFriend.GameInfo is { GameID: var gameId } && gameId > 0 => ServerListScreen.FriendInfo.Status.PlayingAnotherGame,
|
||||
_ => ServerListScreen.FriendInfo.Status.NotPlaying
|
||||
})
|
||||
{
|
||||
ServerName = steamFriend.GetRichPresence("servername"),
|
||||
ConnectCommand = steamFriend.GetRichPresence("connect") is { } connectCmd
|
||||
? ToolBox.ParseConnectCommand(ToolBox.SplitCommand(connectCmd))
|
||||
: Option<ConnectCommand>.None()
|
||||
};
|
||||
|
||||
public override ServerListScreen.FriendInfo[] RetrieveFriends()
|
||||
=> SteamManager.IsInitialized
|
||||
? Steamworks.SteamFriends.GetFriends().Select(FromSteamFriend).ToArray()
|
||||
: Array.Empty<ServerListScreen.FriendInfo>();
|
||||
|
||||
public override void RetrieveAvatar(ServerListScreen.FriendInfo friend, ServerListScreen.AvatarSize avatarSize)
|
||||
{
|
||||
if (!(friend.Id is SteamId steamId)) { return; }
|
||||
|
||||
Func<Steamworks.SteamId, Task<Steamworks.Data.Image?>> avatarFunc = avatarSize switch
|
||||
{
|
||||
ServerListScreen.AvatarSize.Small => Steamworks.SteamFriends.GetSmallAvatarAsync,
|
||||
ServerListScreen.AvatarSize.Medium => Steamworks.SteamFriends.GetMediumAvatarAsync,
|
||||
ServerListScreen.AvatarSize.Large => Steamworks.SteamFriends.GetLargeAvatarAsync,
|
||||
};
|
||||
TaskPool.Add($"Get{avatarSize}AvatarAsync", avatarFunc(steamId.Value), task =>
|
||||
{
|
||||
if (!task.TryGetResult(out Steamworks.Data.Image? img)) { return; }
|
||||
if (!(img is { } avatarImage)) { return; }
|
||||
|
||||
if (friend.Avatar.TryUnwrap(out var prevAvatar))
|
||||
{
|
||||
prevAvatar.Remove();
|
||||
}
|
||||
|
||||
#warning TODO: create an avatar atlas?
|
||||
var avatarTexture = new Texture2D(GameMain.Instance.GraphicsDevice, (int)avatarImage.Width, (int)avatarImage.Height);
|
||||
avatarTexture.SetData(avatarImage.Data);
|
||||
friend.Avatar = Option<Sprite>.Some(new Sprite(avatarTexture, null, null));
|
||||
});
|
||||
}
|
||||
|
||||
public override string GetUserName()
|
||||
=> SteamManager.GetUsername();
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
using Barotrauma.Extensions;
|
||||
using Steamworks.Data;
|
||||
using Color = Microsoft.Xna.Framework.Color;
|
||||
using Socket = System.Net.Sockets.Socket;
|
||||
@@ -34,19 +35,21 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (CoroutineManager.IsCoroutineRunning("ConnectToServer")) { return; }
|
||||
|
||||
switch (serverInfo.Endpoint)
|
||||
var endpointOption = serverInfo.Endpoints.FirstOrNone(e => e is not EosP2PEndpoint);
|
||||
if (!endpointOption.TryUnwrap(out var endpoint)) { return; }
|
||||
|
||||
switch (endpoint)
|
||||
{
|
||||
case LidgrenEndpoint { NetEndpoint: var endPoint }:
|
||||
|
||||
GetIPAddressPing(serverInfo, endPoint, onPingDiscovered);
|
||||
break;
|
||||
case SteamP2PEndpoint steamP2PEndpoint:
|
||||
TaskPool.Add($"EstimateSteamLobbyPing ({steamP2PEndpoint.StringRepresentation})",
|
||||
case SteamP2PEndpoint:
|
||||
TaskPool.Add($"EstimateSteamLobbyPing ({endpoint.StringRepresentation})",
|
||||
EstimateSteamLobbyPing(serverInfo),
|
||||
t =>
|
||||
{
|
||||
if (!t.TryGetResult(out Option<int> ping)) { return; }
|
||||
serverInfo.Ping = ping;
|
||||
if (!t.TryGetResult(out Result<int, SteamLobbyPingError> ping)) { return; }
|
||||
serverInfo.Ping = ping.TryUnwrapSuccess(out var ms) ? Option.Some(ms) : Option.None;
|
||||
onPingDiscovered(serverInfo);
|
||||
});
|
||||
break;
|
||||
@@ -99,35 +102,57 @@ namespace Barotrauma.Networking
|
||||
|
||||
return loadedLobby;
|
||||
}
|
||||
|
||||
private static async Task<Option<int>> EstimateSteamLobbyPing(ServerInfo serverInfo)
|
||||
{
|
||||
if (!(serverInfo.Endpoint is SteamP2PEndpoint { SteamId: var ownerId })) { return Option<int>.None(); }
|
||||
while (!steamPingInfoReady) { await Task.Delay(50); }
|
||||
|
||||
Lobby lobby;
|
||||
private enum SteamLobbyPingError
|
||||
{
|
||||
SteamPingUnsupported,
|
||||
FailedToGetHostLocationData,
|
||||
FailedToParseHostLocationData,
|
||||
PingEstimationFailed
|
||||
}
|
||||
|
||||
private static async Task<Result<int, SteamLobbyPingError>> EstimateSteamLobbyPing(ServerInfo serverInfo)
|
||||
{
|
||||
while (!steamPingInfoReady)
|
||||
{
|
||||
if (!SteamManager.IsInitialized) { return Result.Failure(SteamLobbyPingError.SteamPingUnsupported); }
|
||||
await Task.Delay(50);
|
||||
}
|
||||
|
||||
string pingLocationStr = "";
|
||||
|
||||
if (serverInfo.MetadataSource.TryUnwrap(out SteamP2PServerProvider.DataSource src))
|
||||
{
|
||||
lobby = src.Lobby;
|
||||
var lobby = src.Lobby;
|
||||
pingLocationStr = lobby.GetData("steampinglocation");
|
||||
if (pingLocationStr.IsNullOrEmpty()) { pingLocationStr = lobby.GetData("pinglocation"); }
|
||||
}
|
||||
else
|
||||
else if (serverInfo.MetadataSource.TryUnwrap(out EosServerProvider.DataSource srcEos))
|
||||
{
|
||||
var friendLobby = await GetSteamLobbyForUser(ownerId);
|
||||
if (friendLobby is null) { return Option<int>.None(); }
|
||||
lobby = friendLobby.Value;
|
||||
pingLocationStr = srcEos.SteamPingLocation;
|
||||
}
|
||||
else if (serverInfo.Endpoints.OfType<SteamP2PEndpoint>().FirstOrNone().TryUnwrap(out var steamP2PEndpoint))
|
||||
{
|
||||
var friendLobby = await GetSteamLobbyForUser(steamP2PEndpoint.SteamId);
|
||||
pingLocationStr = friendLobby?.GetData("steampinglocation") ?? "";
|
||||
}
|
||||
|
||||
var pingLocation = NetPingLocation.TryParseFromString(lobby.GetData("pinglocation"));
|
||||
|
||||
if (pingLocationStr.IsNullOrEmpty())
|
||||
{
|
||||
return Result.Failure(SteamLobbyPingError.FailedToGetHostLocationData);
|
||||
}
|
||||
|
||||
var pingLocation = NetPingLocation.TryParseFromString(pingLocationStr);
|
||||
|
||||
if (pingLocation.HasValue && Steamworks.SteamNetworkingUtils.LocalPingLocation.HasValue)
|
||||
{
|
||||
int ping = Steamworks.SteamNetworkingUtils.LocalPingLocation.Value.EstimatePingTo(pingLocation.Value);
|
||||
return ping >= 0 ? Option<int>.Some(ping) : Option<int>.None();
|
||||
if (ping < 0) { return Result.Failure(SteamLobbyPingError.PingEstimationFailed); }
|
||||
return Result.Success(ping);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Option<int>.None();
|
||||
return Result.Failure(SteamLobbyPingError.FailedToParseHostLocationData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +198,7 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
if (endPoint?.Address == null) { return Option<int>.None(); }
|
||||
|
||||
|
||||
//don't attempt to ping if the address is IPv6 and it's not supported
|
||||
if (endPoint.Address.AddressFamily == AddressFamily.InterNetworkV6 && !Socket.OSSupportsIPv6) { return Option<int>.None(); }
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@ namespace Barotrauma.Networking
|
||||
public abstract void Write(XElement element);
|
||||
}
|
||||
|
||||
public Endpoint Endpoint { get; private set; }
|
||||
public ImmutableArray<Endpoint> Endpoints { get; }
|
||||
|
||||
public Option<DataSource> MetadataSource = Option<DataSource>.None();
|
||||
public Option<DataSource> MetadataSource = Option.None;
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public string ServerName { get; set; } = "";
|
||||
@@ -75,6 +75,8 @@ namespace Barotrauma.Networking
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public LanguageIdentifier Language { get; set; }
|
||||
|
||||
public bool EosCrossplay { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public string SelectedSub { get; set; } = string.Empty;
|
||||
|
||||
@@ -84,49 +86,30 @@ namespace Barotrauma.Networking
|
||||
|
||||
public bool Checked = false;
|
||||
|
||||
public readonly struct ContentPackageInfo
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly string Hash;
|
||||
public readonly Option<ContentPackageId> Id;
|
||||
|
||||
public ContentPackageInfo(string name, string hash, Option<ContentPackageId> id)
|
||||
{
|
||||
Name = name;
|
||||
Hash = hash;
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public ContentPackageInfo(ContentPackage pkg)
|
||||
{
|
||||
Name = pkg.Name;
|
||||
Hash = pkg.Hash.StringRepresentation;
|
||||
Id = pkg.UgcId;
|
||||
}
|
||||
}
|
||||
|
||||
public ImmutableArray<ContentPackageInfo> ContentPackages;
|
||||
public ImmutableArray<ServerListContentPackageInfo> ContentPackages;
|
||||
|
||||
public int ContentPackageCount;
|
||||
|
||||
public bool IsModded => ContentPackages.Any(p => !GameMain.VanillaContent.NameMatches(p.Name));
|
||||
|
||||
public ServerInfo(Endpoint endpoint)
|
||||
public ServerInfo(params Endpoint[] endpoint) : this(endpoint.ToImmutableArray()) { }
|
||||
|
||||
public ServerInfo(ImmutableArray<Endpoint> endpoints)
|
||||
{
|
||||
SerializableProperties = SerializableProperty.GetProperties(this);
|
||||
Endpoint = endpoint;
|
||||
ContentPackages = ImmutableArray<ContentPackageInfo>.Empty;
|
||||
Endpoints = endpoints;
|
||||
ContentPackages = ImmutableArray<ServerListContentPackageInfo>.Empty;
|
||||
}
|
||||
|
||||
public static ServerInfo FromServerConnection(NetworkConnection connection, ServerSettings serverSettings)
|
||||
public static ServerInfo FromServerEndpoints(ImmutableArray<Endpoint> endpoints, ServerSettings serverSettings)
|
||||
{
|
||||
var serverInfo = new ServerInfo(connection.Endpoint)
|
||||
var serverInfo = new ServerInfo(endpoints)
|
||||
{
|
||||
GameMode = GameMain.NetLobbyScreen.SelectedMode?.Identifier ?? Identifier.Empty,
|
||||
GameStarted = Screen.Selected != GameMain.NetLobbyScreen,
|
||||
GameVersion = GameMain.Version,
|
||||
PlayerCount = GameMain.Client.ConnectedClients.Count,
|
||||
ContentPackages = ContentPackageManager.EnabledPackages.All.Select(p => new ContentPackageInfo(p)).ToImmutableArray(),
|
||||
ContentPackages = ContentPackageManager.EnabledPackages.All.Select(p => new ServerListContentPackageInfo(p)).ToImmutableArray(),
|
||||
Ping = GameMain.Client.Ping,
|
||||
|
||||
// -------------------------------------
|
||||
@@ -225,7 +208,7 @@ namespace Barotrauma.Networking
|
||||
playStyleName.RectTransform.IsFixedSize = true;
|
||||
|
||||
var serverType = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform),
|
||||
Endpoint?.ServerTypeString ?? string.Empty,
|
||||
Endpoints.First().ServerTypeString,
|
||||
textAlignment: Alignment.TopLeft)
|
||||
{
|
||||
CanBeFocused = false
|
||||
@@ -460,8 +443,12 @@ namespace Barotrauma.Networking
|
||||
GameVersion = version;
|
||||
}
|
||||
if (int.TryParse(valueGetter("playercount"), out int playerCount)) { PlayerCount = playerCount; }
|
||||
if (int.TryParse(valueGetter("maxplayernum"), out int maxPlayers)) { MaxPlayers = maxPlayers; }
|
||||
|
||||
if (int.TryParse(valueGetter("maxplayers"), out int maxPlayers)) { MaxPlayers = maxPlayers; }
|
||||
else if (int.TryParse(valueGetter("maxplayernum"), out maxPlayers)) { MaxPlayers = maxPlayers; }
|
||||
|
||||
if (Enum.TryParse(valueGetter("modeselectionmode"), out SelectionMode modeSelectionMode)) { ModeSelectionMode = modeSelectionMode; }
|
||||
|
||||
if (Enum.TryParse(valueGetter("subselectionmode"), out SelectionMode subSelectionMode)) { SubSelectionMode = subSelectionMode; }
|
||||
|
||||
HasPassword = getBool("haspassword");
|
||||
@@ -471,6 +458,8 @@ namespace Barotrauma.Networking
|
||||
AllowSpectating = getBool("allowspectating");
|
||||
AllowRespawn = getBool("allowrespawn");
|
||||
VoipEnabled = getBool("voicechatenabled");
|
||||
EosCrossplay = getBool("eoscrossplay");
|
||||
|
||||
GameMode = valueGetter("gamemode")?.ToIdentifier() ?? Identifier.Empty;
|
||||
if (float.TryParse(valueGetter("traitors"), NumberStyles.Any, CultureInfo.InvariantCulture, out float traitorProbability)) { TraitorProbability = traitorProbability; }
|
||||
if (Enum.TryParse(valueGetter("playstyle"), out PlayStyle playStyle)) { PlayStyle = playStyle; }
|
||||
@@ -488,27 +477,21 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
private static ContentPackageInfo[] ExtractContentPackageInfo(string serverName, Func<string, string?> valueGetter)
|
||||
private static ServerListContentPackageInfo[] ExtractContentPackageInfo(string serverName, Func<string, string?> valueGetter)
|
||||
{
|
||||
//workaround to ServerRules queries truncating the values to 255 bytes
|
||||
int individualPackageIndex = 0;
|
||||
string? individualPackage = valueGetter($"contentpackage{individualPackageIndex}");
|
||||
if (!individualPackage.IsNullOrEmpty())
|
||||
{
|
||||
List<ContentPackageInfo> contentPackages = new List<ContentPackageInfo>();
|
||||
List<ServerListContentPackageInfo> contentPackages = new List<ServerListContentPackageInfo>();
|
||||
do
|
||||
{
|
||||
string[] splitPackageInfo = individualPackage.Split(',');
|
||||
if (splitPackageInfo.Length != 3)
|
||||
if (!ServerListContentPackageInfo.ParseSingleEntry(individualPackage).TryUnwrap(out var info))
|
||||
{
|
||||
DebugConsole.Log(
|
||||
$"Error in a server's content package list: malformed content package info ({individualPackage}).");
|
||||
return Array.Empty<ContentPackageInfo>();
|
||||
return Array.Empty<ServerListContentPackageInfo>();
|
||||
}
|
||||
string name = splitPackageInfo[0];
|
||||
string hash = splitPackageInfo[1];
|
||||
ulong.TryParse(splitPackageInfo[2], out ulong id);
|
||||
contentPackages.Add(new ContentPackageInfo(name, hash, Option<ContentPackageId>.Some(new SteamWorkshopId(id))));
|
||||
contentPackages.Add(info);
|
||||
|
||||
individualPackageIndex++;
|
||||
individualPackage = valueGetter($"contentpackage{individualPackageIndex}");
|
||||
@@ -518,43 +501,58 @@ namespace Barotrauma.Networking
|
||||
|
||||
string? joinedNames = valueGetter("contentpackage");
|
||||
string? joinedHashes = valueGetter("contentpackagehash");
|
||||
string? joinedWorkshopIds = valueGetter("contentpackageid");
|
||||
string? joinedUgcIds = valueGetter("contentpackageid");
|
||||
|
||||
string[] contentPackageNames = joinedNames.IsNullOrEmpty() ? Array.Empty<string>() : joinedNames.Split(',');
|
||||
string[] contentPackageHashes = joinedHashes.IsNullOrEmpty() ? Array.Empty<string>() : joinedHashes.Split(',');
|
||||
#warning TODO: genericize
|
||||
ulong[] contentPackageIds = joinedWorkshopIds.IsNullOrEmpty() ? new ulong[1] : SteamManager.ParseWorkshopIds(joinedWorkshopIds).ToArray();
|
||||
var contentPackageNames = joinedNames.IsNullOrEmpty() ? Array.Empty<string>() : joinedNames.SplitEscaped(',');
|
||||
var contentPackageHashes = joinedHashes.IsNullOrEmpty() ? Array.Empty<string>() : joinedHashes.SplitEscaped(',');
|
||||
var contentPackageIds = joinedUgcIds.IsNullOrEmpty() ? new string[1] { string.Empty } : joinedUgcIds.SplitEscaped(',');
|
||||
|
||||
if (contentPackageNames.Length != contentPackageHashes.Length || contentPackageHashes.Length != contentPackageIds.Length)
|
||||
if (contentPackageNames.Count != contentPackageHashes.Count || contentPackageHashes.Count != contentPackageIds.Count)
|
||||
{
|
||||
DebugConsole.Log(
|
||||
$"The number of names, hashes and Workshop IDs on server \"{serverName}\"" +
|
||||
$" doesn't match: {contentPackageNames.Length} names ({string.Join(", ", contentPackageNames)}), {contentPackageHashes.Length} hashes, {contentPackageIds.Length} ids)");
|
||||
return Array.Empty<ContentPackageInfo>();
|
||||
$"The number of names, hashes and UGC IDs on server \"{serverName}\"" +
|
||||
$" doesn't match: {contentPackageNames.Count} names ({string.Join(", ", contentPackageNames)}), {contentPackageHashes.Count} hashes, {contentPackageIds.Count} ids)");
|
||||
return Array.Empty<ServerListContentPackageInfo>();
|
||||
}
|
||||
|
||||
return contentPackageNames
|
||||
.Zip(contentPackageHashes, (name, hash) => (name, hash))
|
||||
.Zip(contentPackageIds, (t1, id) =>
|
||||
new ContentPackageInfo(
|
||||
new ServerListContentPackageInfo(
|
||||
t1.name,
|
||||
t1.hash,
|
||||
Option<ContentPackageId>.Some(new SteamWorkshopId(id))))
|
||||
ContentPackageId.Parse(id)))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public static Option<ServerInfo> FromXElement(XElement element)
|
||||
{
|
||||
var endpoints = new List<Endpoint>();
|
||||
|
||||
string endpointStr
|
||||
= element.GetAttributeString("Endpoint", null)
|
||||
?? element.GetAttributeString("OwnerID", null)
|
||||
?? $"{element.GetAttributeString("IP", "")}:{element.GetAttributeInt("Port", 0)}";
|
||||
|
||||
if (Endpoint.Parse(endpointStr).TryUnwrap(out var endpoint))
|
||||
{
|
||||
endpoints.Add(endpoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
var multipleEndpointStrs
|
||||
= element.GetAttributeStringArray("Endpoints", Array.Empty<string>());
|
||||
endpoints.AddRange(
|
||||
multipleEndpointStrs
|
||||
.Select(Endpoint.Parse)
|
||||
.NotNone());
|
||||
}
|
||||
|
||||
if (!Endpoint.Parse(endpointStr).TryUnwrap(out var endpoint)) { return Option<ServerInfo>.None(); }
|
||||
if (endpoints.Count == 0) { return Option.None; }
|
||||
|
||||
var gameVersionStr = element.GetAttributeString("GameVersion", "");
|
||||
if (!Version.TryParse(gameVersionStr, out var gameVersion)) { gameVersion = GameMain.Version; }
|
||||
var info = new ServerInfo(endpoint)
|
||||
var info = new ServerInfo(endpoints.ToImmutableArray())
|
||||
{
|
||||
GameVersion = gameVersion
|
||||
};
|
||||
@@ -562,14 +560,14 @@ namespace Barotrauma.Networking
|
||||
|
||||
info.MetadataSource = DataSource.Parse(element);
|
||||
|
||||
return Option<ServerInfo>.Some(info);
|
||||
return Option.Some(info);
|
||||
}
|
||||
|
||||
public XElement ToXElement()
|
||||
{
|
||||
XElement element = new XElement(GetType().Name);
|
||||
|
||||
element.SetAttributeValue("Endpoint", Endpoint.ToString());
|
||||
element.SetAttributeValue("Endpoints", string.Join(",", Endpoints.Select(e => e.StringRepresentation)));
|
||||
element.SetAttributeValue("GameVersion", GameVersion.ToString());
|
||||
|
||||
SerializableProperty.SerializeProperties(this, element, saveIfDefault: true);
|
||||
@@ -588,9 +586,9 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
public bool Equals(ServerInfo other)
|
||||
=> other.Endpoint == Endpoint;
|
||||
=> other.Endpoints.Any(e => Endpoints.Contains(e));
|
||||
|
||||
public override int GetHashCode() => Endpoint.GetHashCode();
|
||||
public override int GetHashCode() => Endpoints.First().GetHashCode();
|
||||
|
||||
string ISerializableEntity.Name => "ServerInfo";
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; }
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ namespace Barotrauma
|
||||
this.providers = providers.ToImmutableArray();
|
||||
}
|
||||
|
||||
protected override void RetrieveServersImpl(Action<ServerInfo> onServerDataReceived, Action onQueryCompleted)
|
||||
protected override void RetrieveServersImpl(Action<ServerInfo, ServerProvider> onServerDataReceived, Action onQueryCompleted)
|
||||
{
|
||||
int providersFinished = 0;
|
||||
void ackFinishedProvider()
|
||||
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
sealed class EosServerProvider : ServerProvider
|
||||
{
|
||||
public sealed class DataSource : ServerInfo.DataSource
|
||||
{
|
||||
public readonly string SteamPingLocation;
|
||||
|
||||
public DataSource(string steamPingLocation)
|
||||
{
|
||||
SteamPingLocation = steamPingLocation;
|
||||
}
|
||||
|
||||
public override void Write(XElement element) { /* do nothing */ }
|
||||
}
|
||||
|
||||
protected override void RetrieveServersImpl(Action<ServerInfo, ServerProvider> onServerDataReceived, Action onQueryCompleted)
|
||||
{
|
||||
if (EosInterface.IdQueries.GetLoggedInPuids() is not { Length: > 0 } loggedInPuids) { return; }
|
||||
|
||||
int finishedTaskCount = 0;
|
||||
int totalTaskCount = EosInterface.Sessions.MaxBucketIndex + 1 - EosInterface.Sessions.MinBucketIndex;
|
||||
|
||||
void countTaskFinished()
|
||||
{
|
||||
finishedTaskCount++;
|
||||
if (finishedTaskCount == totalTaskCount)
|
||||
{
|
||||
onQueryCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
void onTaskFinished(Task t)
|
||||
{
|
||||
using var janitor = Janitor.Start();
|
||||
janitor.AddAction(countTaskFinished);
|
||||
|
||||
if (!t.TryGetResult(
|
||||
out Result<ImmutableArray<EosInterface.Sessions.RemoteSession>, EosInterface.Sessions.RemoteSession.Query.Error>? result))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.TryUnwrapSuccess(out var sessions))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var addedEndpoints = new HashSet<Endpoint>();
|
||||
foreach (var session in sessions)
|
||||
{
|
||||
if (!session.Attributes.TryGetValue("ServerName".ToIdentifier(), out var serverName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var endpointOption = Endpoint.Parse(session.HostAddress);
|
||||
if (!endpointOption.TryUnwrap(out var primaryEndpoint))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var endpoints = new List<Endpoint> { primaryEndpoint };
|
||||
if (primaryEndpoint is EosP2PEndpoint
|
||||
&& session.Attributes.TryGetValue("SteamP2PEndpoint".ToIdentifier(), out var steamIdStr)
|
||||
&& SteamP2PEndpoint.Parse(steamIdStr).TryUnwrap(out var steamP2PEndpoint))
|
||||
{
|
||||
endpoints.Add(steamP2PEndpoint);
|
||||
}
|
||||
else if (primaryEndpoint is LidgrenEndpoint
|
||||
{
|
||||
Address: LidgrenAddress address, Port: NetConfig.DefaultPort
|
||||
}
|
||||
&& session.Attributes.TryGetValue("Port".ToIdentifier(), out var portStr)
|
||||
&& ushort.TryParse(portStr, out var port))
|
||||
{
|
||||
// Port isn't included as part of the host address
|
||||
// because it's filled in by EOS automatically,
|
||||
// so extract the port from a separate attribute and
|
||||
// fix up the endpoint here
|
||||
primaryEndpoint = new LidgrenEndpoint(address.NetAddress, port);
|
||||
endpoints[0] = primaryEndpoint;
|
||||
}
|
||||
|
||||
// Prevent duplicate entries
|
||||
if (endpoints.Intersect(addedEndpoints).Any())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
addedEndpoints.UnionWith(endpoints);
|
||||
|
||||
var serverInfo = new ServerInfo(endpoints.ToImmutableArray())
|
||||
{
|
||||
ServerName = serverName
|
||||
};
|
||||
serverInfo.UpdateInfo(key =>
|
||||
session.Attributes.TryGetValue(key.ToIdentifier(), out var value) ? value : string.Empty);
|
||||
serverInfo.EosCrossplay = true;
|
||||
serverInfo.Checked = true;
|
||||
|
||||
if (session.Attributes.TryGetValue("steampinglocation".ToIdentifier(), out var steamPingLocation))
|
||||
{
|
||||
serverInfo.MetadataSource = Option.Some((ServerInfo.DataSource)new DataSource(steamPingLocation));
|
||||
}
|
||||
|
||||
onServerDataReceived(serverInfo, this);
|
||||
}
|
||||
};
|
||||
|
||||
for (int bucketIndex = EosInterface.Sessions.MinBucketIndex; bucketIndex <= EosInterface.Sessions.MaxBucketIndex; bucketIndex++)
|
||||
{
|
||||
var query = new EosInterface.Sessions.RemoteSession.Query(
|
||||
BucketIndex: bucketIndex,
|
||||
LocalUserId: loggedInPuids.First(),
|
||||
MaxResults: 200,
|
||||
Attributes: ImmutableDictionary<Identifier, string>.Empty);
|
||||
|
||||
TaskPool.Add(
|
||||
$"{nameof(EosServerProvider)}.{nameof(RetrieveServersImpl)}",
|
||||
query.Run(),
|
||||
onTaskFinished);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Cancel()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -6,12 +6,12 @@ namespace Barotrauma
|
||||
{
|
||||
abstract class ServerProvider
|
||||
{
|
||||
public void RetrieveServers(Action<ServerInfo> onServerDataReceived, Action onQueryCompleted)
|
||||
public void RetrieveServers(Action<ServerInfo, ServerProvider> onServerDataReceived, Action onQueryCompleted)
|
||||
{
|
||||
Cancel();
|
||||
RetrieveServersImpl(onServerDataReceived, onQueryCompleted);
|
||||
}
|
||||
protected abstract void RetrieveServersImpl(Action<ServerInfo> onServerDataReceived, Action onQueryCompleted);
|
||||
protected abstract void RetrieveServersImpl(Action<ServerInfo, ServerProvider> action, Action onQueryCompleted);
|
||||
public abstract void Cancel();
|
||||
}
|
||||
}
|
||||
+10
-9
@@ -46,42 +46,43 @@ namespace Barotrauma
|
||||
MetadataSource = Option<ServerInfo.DataSource>.Some(new DataSource((UInt16)entry.QueryPort))
|
||||
});
|
||||
|
||||
private static void HandleResponsiveServer(Steamworks.Data.ServerInfo entry, Action<ServerInfo> onServerDataReceived)
|
||||
private void HandleResponsiveServer(Steamworks.Data.ServerInfo entry, Action<ServerInfo, ServerProvider> onServerDataReceived)
|
||||
{
|
||||
TaskPool.Add($"QueryServerRules (GetServers, {entry.Name}, {entry.Address})", entry.QueryRulesAsync(),
|
||||
t =>
|
||||
{
|
||||
if (t.Status == TaskStatus.Faulted)
|
||||
{
|
||||
TaskPool.PrintTaskExceptions(t, $"Failed to retrieve rules for {entry.Name}");
|
||||
TaskPool.PrintTaskExceptions(t, $"Failed to retrieve rules for {entry.Name}", msg => DebugConsole.ThrowError(msg));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!t.TryGetResult(out Dictionary<string, string> rules)) { return; }
|
||||
if (!t.TryGetResult(out Dictionary<string, string>? rules)) { return; }
|
||||
if (rules is null) { return; }
|
||||
|
||||
if (!InfoFromListEntry(entry).TryUnwrap(out var serverInfo)) { return; }
|
||||
serverInfo.UpdateInfo(key =>
|
||||
{
|
||||
if (rules.TryGetValue(key, out var val)) { return val; }
|
||||
return null;
|
||||
});
|
||||
serverInfo.Checked = true; //rules != null;
|
||||
serverInfo.Checked = true;
|
||||
|
||||
onServerDataReceived(serverInfo);
|
||||
onServerDataReceived(serverInfo, this);
|
||||
});
|
||||
}
|
||||
|
||||
private static void HandleUnresponsiveServer(Steamworks.Data.ServerInfo entry, Action<ServerInfo> onServerDataReceived)
|
||||
private void HandleUnresponsiveServer(Steamworks.Data.ServerInfo entry, Action<ServerInfo, ServerProvider> onServerDataReceived)
|
||||
{
|
||||
//TODO: do we still want to list unresponsive servers?
|
||||
if (!InfoFromListEntry(entry).TryUnwrap(out var serverInfo)) { return; }
|
||||
onServerDataReceived(serverInfo);
|
||||
onServerDataReceived(serverInfo, this);
|
||||
}
|
||||
|
||||
private Steamworks.ServerList.Internet? serverQuery;
|
||||
private CoroutineHandle? queryCoroutine;
|
||||
|
||||
protected override void RetrieveServersImpl(Action<ServerInfo> onServerDataReceived, Action onQueryCompleted)
|
||||
protected override void RetrieveServersImpl(Action<ServerInfo, ServerProvider> onServerDataReceived, Action onQueryCompleted)
|
||||
{
|
||||
if (!SteamManager.IsInitialized)
|
||||
{
|
||||
@@ -139,7 +140,7 @@ namespace Barotrauma
|
||||
CoroutineManager.StopCoroutines(selfQueryCoroutine);
|
||||
dequeue();
|
||||
|
||||
if (t.Status == TaskStatus.Faulted) { TaskPool.PrintTaskExceptions(t, "Failed to retrieve servers"); }
|
||||
if (t.Status == TaskStatus.Faulted) { TaskPool.PrintTaskExceptions(t, "Failed to retrieve servers", msg => DebugConsole.ThrowError(msg)); }
|
||||
|
||||
selfServerQuery.Dispose();
|
||||
}
|
||||
|
||||
+13
-5
@@ -1,6 +1,7 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
@@ -24,7 +25,7 @@ namespace Barotrauma
|
||||
|
||||
private object? queryRef = null;
|
||||
|
||||
protected override void RetrieveServersImpl(Action<ServerInfo> onServerDataReceived, Action onQueryCompleted)
|
||||
protected override void RetrieveServersImpl(Action<ServerInfo, ServerProvider> onServerDataReceived, Action onQueryCompleted)
|
||||
{
|
||||
if (!SteamManager.IsInitialized)
|
||||
{
|
||||
@@ -61,7 +62,7 @@ namespace Barotrauma
|
||||
// If queryRef != selfQueryRef, this query was cancelled
|
||||
if (!ReferenceEquals(selfQueryRef, queryRef)) { return; }
|
||||
|
||||
if (!t.TryGetResult(out Steamworks.Data.Lobby[] lobbies)
|
||||
if (!t.TryGetResult(out Steamworks.Data.Lobby[]? lobbies)
|
||||
|| lobbies is null
|
||||
|| lobbies.Length == 0)
|
||||
{
|
||||
@@ -74,16 +75,23 @@ namespace Barotrauma
|
||||
string lobbyOwnerStr = lobby.GetData("lobbyowner") ?? "";
|
||||
lobbyQuery = lobbyQuery.WithoutKeyValue("lobbyowner", lobbyOwnerStr);
|
||||
|
||||
string serverName = lobby.GetData("name") ?? "";
|
||||
string serverName = lobby.GetData("servername").FallbackNullOrEmpty(lobby.GetData("name")) ?? "";
|
||||
if (string.IsNullOrEmpty(serverName)) { continue; }
|
||||
|
||||
var ownerId = SteamId.Parse(lobbyOwnerStr);
|
||||
if (!ownerId.TryUnwrap(out var lobbyOwnerId)) { continue; }
|
||||
|
||||
var eosP2PEndpointOption = EosP2PEndpoint
|
||||
.Parse(lobby.GetData("EosEndpoint") ?? "")
|
||||
.Select(e => (Endpoint)e);
|
||||
|
||||
if (retrieved.Contains(lobbyOwnerId)) { continue; }
|
||||
retrieved.Add(lobbyOwnerId);
|
||||
|
||||
var serverInfo = new ServerInfo(new SteamP2PEndpoint(lobbyOwnerId))
|
||||
var endpoints = new List<Endpoint> { new SteamP2PEndpoint(lobbyOwnerId) };
|
||||
if (eosP2PEndpointOption.TryUnwrap(out var eosP2PEndpoint)) { endpoints.Add(eosP2PEndpoint); }
|
||||
|
||||
var serverInfo = new ServerInfo(endpoints.ToImmutableArray())
|
||||
{
|
||||
ServerName = serverName,
|
||||
MetadataSource = Option<ServerInfo.DataSource>.Some(new DataSource(lobby))
|
||||
@@ -91,7 +99,7 @@ namespace Barotrauma
|
||||
serverInfo.UpdateInfo(key => lobby.GetData(key));
|
||||
serverInfo.Checked = true;
|
||||
|
||||
onServerDataReceived(serverInfo);
|
||||
onServerDataReceived(serverInfo, this);
|
||||
}
|
||||
startQuery();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user