Unstable v0.19.3.0

This commit is contained in:
Juan Pablo Arce
2022-09-02 15:10:56 -03:00
parent 28789616bd
commit 3f2c843247
336 changed files with 7152 additions and 7739 deletions
@@ -1,60 +1,23 @@
#nullable enable
using Barotrauma.Steam;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
namespace Barotrauma.Networking
{
abstract class ClientPeer
internal abstract class ClientPeer
{
public class ServerContentPackage
{
public readonly string Name;
public readonly Md5Hash Hash;
public readonly UInt64 WorkshopId;
public readonly DateTime InstallTime;
public RegularPackage? RegularPackage
{
get
{
return ContentPackageManager.RegularPackages.FirstOrDefault(p => p.Hash.Equals(Hash));
}
}
public CorePackage? CorePackage
{
get
{
return ContentPackageManager.CorePackages.FirstOrDefault(p => p.Hash.Equals(Hash));
}
}
public ContentPackage? ContentPackage
=> (ContentPackage?)RegularPackage ?? CorePackage;
public string GetPackageStr()
=> $"\"{Name}\" (hash {Hash.ShortRepresentation})";
public ServerContentPackage(string name, Md5Hash hash, UInt64 workshopId, DateTime installTime)
{
Name = name;
Hash = hash;
WorkshopId = workshopId;
InstallTime = installTime;
}
}
public ImmutableArray<ServerContentPackage> ServerContentPackages { get; set; } =
ImmutableArray<ServerContentPackage>.Empty;
public delegate void MessageCallback(IReadMessage message);
public delegate void DisconnectCallback(bool disableReconnect);
public delegate void DisconnectMessageCallback(string message);
public delegate void PasswordCallback(int salt, int retries);
public delegate void InitializationCompleteCallback();
[Obsolete("TODO: delete in nr3-layer-1-2-cleanup")]
@@ -65,8 +28,12 @@ namespace Barotrauma.Networking
public readonly DisconnectMessageCallback OnDisconnectMessageReceived;
public readonly PasswordCallback OnRequestPassword;
public readonly InitializationCompleteCallback OnInitializationComplete;
public Callbacks(MessageCallback onMessageReceived, DisconnectCallback onDisconnect, DisconnectMessageCallback onDisconnectMessageReceived, PasswordCallback onRequestPassword, InitializationCompleteCallback onInitializationComplete)
public Callbacks(MessageCallback onMessageReceived,
DisconnectCallback onDisconnect,
DisconnectMessageCallback onDisconnectMessageReceived,
PasswordCallback onRequestPassword,
InitializationCompleteCallback onInitializationComplete)
{
OnMessageReceived = onMessageReceived;
OnDisconnect = onDisconnect;
@@ -91,98 +58,106 @@ namespace Barotrauma.Networking
this.ownerKey = ownerKey;
isOwner = ownerKey.IsSome();
}
public abstract void Start();
public abstract void Close(string? msg = null, bool disableReconnect = false);
public abstract void Update(float deltaTime);
public abstract void Send(IWriteMessage msg, DeliveryMethod deliveryMethod, bool compressPastThreshold = true);
public abstract void SendPassword(string password);
protected abstract void SendMsgInternal(DeliveryMethod deliveryMethod, IWriteMessage msg);
protected abstract void SendMsgInternal(PeerPacketHeaders headers, INetSerializableStruct? body);
protected ConnectionInitialization initializationStep;
public bool ContentPackageOrderReceived { get; protected set; }
protected int passwordSalt;
protected Steamworks.AuthTicket? steamAuthTicket;
protected void ReadConnectionInitializationStep(IReadMessage inc)
public struct IncomingInitializationMessage
{
ConnectionInitialization step = (ConnectionInitialization)inc.ReadByte();
public ConnectionInitialization InitializationStep;
public IReadMessage Message;
}
IWriteMessage outMsg;
switch (step)
protected void ReadConnectionInitializationStep(IncomingInitializationMessage inc)
{
switch (inc.InitializationStep)
{
case ConnectionInitialization.SteamTicketAndVersion:
{
if (initializationStep != ConnectionInitialization.SteamTicketAndVersion) { return; }
outMsg = new WriteOnlyMessage();
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
outMsg.Write((byte)ConnectionInitialization.SteamTicketAndVersion);
outMsg.Write(GameMain.Client.Name);
outMsg.Write(ownerKey.Fallback(0));
outMsg.Write(SteamManager.GetSteamId().Select(steamId => steamId.Value).Fallback(0));
if (steamAuthTicket == null)
{
outMsg.Write((UInt16)0);
}
else
{
outMsg.Write((UInt16)steamAuthTicket.Data.Length);
outMsg.Write(steamAuthTicket.Data, 0, steamAuthTicket.Data.Length);
}
outMsg.Write(GameMain.Version.ToString());
outMsg.Write(GameSettings.CurrentConfig.Language.Value);
SendMsgInternal(DeliveryMethod.Reliable, outMsg);
PeerPacketHeaders headers = new PeerPacketHeaders
{
DeliveryMethod = DeliveryMethod.Reliable,
PacketHeader = PacketHeader.IsConnectionInitializationStep,
Initialization = ConnectionInitialization.SteamTicketAndVersion
};
ClientSteamTicketAndVersionPacket body = new ClientSteamTicketAndVersionPacket
{
Name = GameMain.Client.Name,
OwnerKey = ownerKey,
SteamId = SteamManager.GetSteamId().Select(id => (AccountId)id),
SteamAuthTicket = steamAuthTicket switch
{
null => Option<byte[]>.None(),
var ticket => Option<byte[]>.Some(ticket.Data)
},
GameVersion = GameMain.Version.ToString(),
Language = GameSettings.CurrentConfig.Language.Value
};
SendMsgInternal(headers, body);
break;
}
case ConnectionInitialization.ContentPackageOrder:
{
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion ||
initializationStep == ConnectionInitialization.Password) { initializationStep = ConnectionInitialization.ContentPackageOrder; }
if (initializationStep != ConnectionInitialization.ContentPackageOrder) { return; }
outMsg = new WriteOnlyMessage();
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
outMsg.Write((byte)ConnectionInitialization.ContentPackageOrder);
UInt32 packageCount = inc.ReadVariableUInt32();
List<ServerContentPackage> serverPackages = new List<ServerContentPackage>();
for (int i = 0; i < packageCount; i++)
initializationStep == ConnectionInitialization.Password)
{
string name = inc.ReadString();
UInt32 hashByteCount = inc.ReadVariableUInt32();
byte[] hashBytes = inc.ReadBytes((int)hashByteCount);
UInt64 workshopId = inc.ReadUInt64();
UInt32 installTimeDiffSeconds = inc.ReadUInt32();
DateTime installTime = DateTime.UtcNow + TimeSpan.FromSeconds(installTimeDiffSeconds);
var pkg = new ServerContentPackage(name, Md5Hash.BytesAsHash(hashBytes), workshopId, installTime);
serverPackages.Add(pkg);
initializationStep = ConnectionInitialization.ContentPackageOrder;
}
if (initializationStep != ConnectionInitialization.ContentPackageOrder) { return; }
PeerPacketHeaders headers = new PeerPacketHeaders
{
DeliveryMethod = DeliveryMethod.Reliable,
PacketHeader = PacketHeader.IsConnectionInitializationStep,
Initialization = ConnectionInitialization.ContentPackageOrder
};
var orderPacket = INetSerializableStruct.Read<ServerPeerContentPackageOrderPacket>(inc.Message);
if (!ContentPackageOrderReceived)
{
ServerContentPackages = serverPackages.ToImmutableArray();
if (serverPackages.Count == 0)
ServerContentPackages = orderPacket.ContentPackages;
if (ServerContentPackages.Length == 0)
{
string errorMsg = "Error in ContentPackageOrder message: list of content packages enabled on the server was empty.";
GameAnalyticsManager.AddErrorEventOnce("ClientPeer.ReadConnectionInitializationStep:NoContentPackages", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
DebugConsole.ThrowError(errorMsg);
}
ContentPackageOrderReceived = true;
SendMsgInternal(DeliveryMethod.Reliable, outMsg);
SendMsgInternal(headers, null);
}
break;
}
case ConnectionInitialization.Password:
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion) { initializationStep = ConnectionInitialization.Password; }
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion)
{
initializationStep = ConnectionInitialization.Password;
}
if (initializationStep != ConnectionInitialization.Password) { return; }
bool incomingSalt = inc.ReadBoolean(); inc.ReadPadBits();
int retries = 0;
if (incomingSalt)
{
passwordSalt = inc.ReadInt32();
}
else
{
retries = inc.ReadInt32();
}
var passwordPacket = INetSerializableStruct.Read<ServerPeerPasswordPacket>(inc.Message);
passwordPacket.Salt.TryUnwrap(out passwordSalt);
passwordPacket.RetriesLeft.TryUnwrap(out var retries);
callbacks.OnRequestPassword.Invoke(passwordSalt, retries);
break;
}
@@ -192,4 +167,4 @@ namespace Barotrauma.Networking
public abstract void ForceTimeOut();
#endif
}
}
}
@@ -1,21 +1,22 @@
using Barotrauma.Steam;
using Lidgren.Network;
#nullable enable
using System;
using System.Collections.Generic;
using System.Text;
using Lidgren.Network;
using Barotrauma.Steam;
namespace Barotrauma.Networking
{
class LidgrenClientPeer : ClientPeer
internal sealed class LidgrenClientPeer : ClientPeer
{
private bool isActive;
private NetClient netClient;
private NetPeerConfiguration netPeerConfiguration;
private NetClient? netClient;
private readonly NetPeerConfiguration netPeerConfiguration;
List<NetIncomingMessage> incomingLidgrenMessages;
private readonly List<NetIncomingMessage> incomingLidgrenMessages;
private LidgrenEndpoint lidgrenEndpoint
=> ServerConnection is LidgrenConnection { Endpoint: LidgrenEndpoint result }
private LidgrenEndpoint lidgrenEndpoint =>
ServerConnection is LidgrenConnection { Endpoint: LidgrenEndpoint result }
? result
: throw new InvalidOperationException();
@@ -25,13 +26,6 @@ namespace Barotrauma.Networking
netClient = null;
isActive = false;
}
public override void Start()
{
if (isActive) { return; }
ContentPackageOrderReceived = false;
netPeerConfiguration = new NetPeerConfiguration("barotrauma")
{
@@ -45,6 +39,17 @@ namespace Barotrauma.Networking
| NetIncomingMessageType.ErrorMessage
| NetIncomingMessageType.Error);
incomingLidgrenMessages = new List<NetIncomingMessage>();
}
public override void Start()
{
if (isActive) { return; }
incomingLidgrenMessages.Clear();
ContentPackageOrderReceived = false;
netClient = new NetClient(netPeerConfiguration);
if (SteamManager.IsInitialized)
@@ -56,14 +61,13 @@ namespace Barotrauma.Networking
}
}
incomingLidgrenMessages = new List<NetIncomingMessage>();
initializationStep = ConnectionInitialization.SteamTicketAndVersion;
if (!(ServerEndpoint is LidgrenEndpoint lidgrenEndpoint))
if (!(ServerEndpoint is LidgrenEndpoint lidgrenEndpointValue))
{
throw new InvalidCastException("endPoint is not IPEndPoint");
throw new InvalidCastException($"Endpoint is not {nameof(LidgrenEndpoint)}");
}
if (ServerConnection != null)
{
throw new InvalidOperationException("ServerConnection is not null");
@@ -71,8 +75,8 @@ namespace Barotrauma.Networking
netClient.Start();
var netConnection = netClient.Connect(lidgrenEndpoint.NetEndpoint);
var netConnection = netClient.Connect(lidgrenEndpointValue.NetEndpoint);
ServerConnection = new LidgrenConnection(netConnection)
{
Status = NetworkConnectionStatus.Connected
@@ -85,11 +89,18 @@ namespace Barotrauma.Networking
{
if (!isActive) { return; }
ToolBox.ThrowIfNull(netClient);
ToolBox.ThrowIfNull(incomingLidgrenMessages);
if (isOwner && !(ChildServerRelay.Process is { HasExited: false }))
{
Close();
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage);
msgBox.Buttons[0].OnClicked += (btn, obj) => { GameMain.MainMenuScreen.Select(); return false; };
msgBox.Buttons[0].OnClicked += (btn, obj) =>
{
GameMain.MainMenuScreen.Select();
return false;
};
return;
}
@@ -101,7 +112,11 @@ namespace Barotrauma.Networking
foreach (NetIncomingMessage inc in incomingLidgrenMessages)
{
if (!inc.SenderConnection.RemoteEndPoint.Equals(lidgrenEndpoint.NetEndpoint)) { continue; }
if (!inc.SenderConnection.RemoteEndPoint.Equals(lidgrenEndpoint.NetEndpoint))
{
DebugConsole.AddWarning($"Mismatched endpoint: expected {lidgrenEndpoint.NetEndpoint}, got {inc.SenderConnection.RemoteEndPoint}");
continue;
}
switch (inc.MessageType)
{
@@ -115,15 +130,23 @@ namespace Barotrauma.Networking
}
}
private void HandleDataMessage(NetIncomingMessage inc)
private void HandleDataMessage(NetIncomingMessage lidgrenMsg)
{
if (!isActive) { return; }
PacketHeader packetHeader = (PacketHeader)inc.ReadByte();
ToolBox.ThrowIfNull(ServerConnection);
IReadMessage inc = lidgrenMsg.ToReadMessage();
var (_, packetHeader, initialization) = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
if (packetHeader.IsConnectionInitializationStep() && initializationStep != ConnectionInitialization.Success)
{
ReadConnectionInitializationStep(new ReadWriteMessage(inc.Data, (int)inc.Position, inc.LengthBits, false));
ReadConnectionInitializationStep(new IncomingInitializationMessage
{
InitializationStep = initialization ?? throw new Exception("Initialization step missing"),
Message = inc
});
}
else
{
@@ -132,9 +155,9 @@ namespace Barotrauma.Networking
callbacks.OnInitializationComplete.Invoke();
initializationStep = ConnectionInitialization.Success;
}
UInt16 length = inc.ReadUInt16();
IReadMessage msg = new ReadOnlyMessage(inc.Data, packetHeader.IsCompressed(), inc.PositionInBytes, length, ServerConnection);
callbacks.OnMessageReceived.Invoke(msg);
var packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
callbacks.OnMessageReceived.Invoke(packet.GetReadMessage(packetHeader.IsCompressed(), ServerConnection));
}
}
@@ -142,7 +165,7 @@ namespace Barotrauma.Networking
{
if (!isActive) { return; }
NetConnectionStatus status = (NetConnectionStatus)inc.ReadByte();
NetConnectionStatus status = inc.ReadHeader<NetConnectionStatus>();
switch (status)
{
case NetConnectionStatus.Disconnected:
@@ -157,29 +180,38 @@ namespace Barotrauma.Networking
{
if (!isActive) { return; }
ToolBox.ThrowIfNull(netClient);
if (initializationStep != ConnectionInitialization.Password) { return; }
NetOutgoingMessage outMsg = netClient.CreateMessage();
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
outMsg.Write((byte)ConnectionInitialization.Password);
byte[] saltedPw = ServerSettings.SaltPassword(Encoding.UTF8.GetBytes(password), passwordSalt);
outMsg.Write((byte)saltedPw.Length);
outMsg.Write(saltedPw, 0, saltedPw.Length);
NetSendResult result = netClient.SendMessage(outMsg, NetDeliveryMethod.ReliableUnordered);
if (result != NetSendResult.Queued && result != NetSendResult.Sent)
var headers = new PeerPacketHeaders
{
DebugConsole.NewMessage("Failed to send " + initializationStep.ToString() + " message to host: " + result);
}
DeliveryMethod = DeliveryMethod.Reliable,
PacketHeader = PacketHeader.IsConnectionInitializationStep,
Initialization = ConnectionInitialization.Password
};
var body = new ClientPeerPasswordPacket
{
Password = ServerSettings.SaltPassword(Encoding.UTF8.GetBytes(password), passwordSalt)
};
SendMsgInternal(headers, body);
}
public override void Close(string msg = null, bool disableReconnect = false)
public override void Close(string? msg = null, bool disableReconnect = false)
{
if (!isActive) { return; }
ToolBox.ThrowIfNull(netClient);
isActive = false;
netClient.Shutdown(msg ?? TextManager.Get("Disconnecting").Value);
netClient = null;
steamAuthTicket?.Cancel(); steamAuthTicket = null;
steamAuthTicket?.Cancel();
steamAuthTicket = null;
callbacks.OnDisconnect.Invoke(disableReconnect);
}
@@ -187,19 +219,8 @@ namespace Barotrauma.Networking
{
if (!isActive) { return; }
NetDeliveryMethod lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable;
switch (deliveryMethod)
{
case DeliveryMethod.Unreliable:
lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable;
break;
case DeliveryMethod.Reliable:
lidgrenDeliveryMethod = NetDeliveryMethod.ReliableUnordered;
break;
case DeliveryMethod.ReliableOrdered:
lidgrenDeliveryMethod = NetDeliveryMethod.ReliableOrdered;
break;
}
ToolBox.ThrowIfNull(netClient);
ToolBox.ThrowIfNull(netPeerConfiguration);
#if DEBUG
netPeerConfiguration.SimulatedDuplicatesChance = GameMain.Client.SimulatedDuplicatesChance;
@@ -208,30 +229,42 @@ namespace Barotrauma.Networking
netPeerConfiguration.SimulatedLoss = GameMain.Client.SimulatedLoss;
#endif
NetOutgoingMessage lidgrenMsg = netClient.CreateMessage();
byte[] msgData = new byte[msg.LengthBytes];
msg.PrepareForSending(ref msgData, compressPastThreshold, out bool isCompressed, out int length);
lidgrenMsg.Write((byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None));
lidgrenMsg.Write((UInt16)length);
lidgrenMsg.Write(msgData, 0, length);
byte[] bufAux = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _);
NetSendResult result = netClient.SendMessage(lidgrenMsg, lidgrenDeliveryMethod);
var headers = new PeerPacketHeaders
{
DeliveryMethod = deliveryMethod,
PacketHeader = isCompressed ? PacketHeader.IsCompressed : PacketHeader.None,
Initialization = null
};
var body = new PeerPacketMessage
{
Buffer = bufAux
};
SendMsgInternal(headers, body);
}
protected override void SendMsgInternal(PeerPacketHeaders headers, INetSerializableStruct? body)
{
ToolBox.ThrowIfNull(netClient);
IWriteMessage msg = new WriteOnlyMessage();
msg.WriteNetSerializableStruct(headers);
body?.Write(msg);
NetSendResult result = ForwardToLidgren(msg, DeliveryMethod.Reliable);
if (result != NetSendResult.Queued && result != NetSendResult.Sent)
{
DebugConsole.NewMessage("Failed to send message to host: " + result);
DebugConsole.NewMessage($"Failed to send message to host: {result}\n{Environment.StackTrace}");
}
}
protected override void SendMsgInternal(DeliveryMethod deliveryMethod, IWriteMessage msg)
private NetSendResult ForwardToLidgren(IWriteMessage msg, DeliveryMethod deliveryMethod)
{
NetOutgoingMessage lidgrenMsg = netClient.CreateMessage();
lidgrenMsg.Write(msg.Buffer, 0, msg.LengthBytes);
ToolBox.ThrowIfNull(netClient);
NetSendResult result = netClient.SendMessage(lidgrenMsg, NetDeliveryMethod.ReliableUnordered);
if (result != NetSendResult.Queued && result != NetSendResult.Sent)
{
DebugConsole.NewMessage("Failed to send message to host: " + result + "\n" + Environment.StackTrace);
}
return netClient.SendMessage(msg.ToLidgren(netClient), deliveryMethod.ToLidgren());
}
#if DEBUG
@@ -241,4 +274,4 @@ namespace Barotrauma.Networking
}
#endif
}
}
}
@@ -1,13 +1,14 @@
using Barotrauma.Steam;
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Barotrauma.Steam;
using System.Threading;
namespace Barotrauma.Networking
{
class SteamP2PClientPeer : ClientPeer
internal sealed class SteamP2PClientPeer : ClientPeer
{
private bool isActive;
private readonly SteamId hostSteamId;
@@ -17,20 +18,20 @@ namespace Barotrauma.Networking
private long sentBytes, receivedBytes;
private List<IReadMessage> incomingInitializationMessages;
private List<IReadMessage> incomingDataMessages;
private readonly List<IncomingInitializationMessage> incomingInitializationMessages = new List<IncomingInitializationMessage>();
private readonly List<IReadMessage> incomingDataMessages = new List<IReadMessage>();
public SteamP2PClientPeer(SteamP2PEndpoint endpoint, Callbacks callbacks) : base(endpoint, callbacks, Option<int>.None())
{
ServerConnection = null;
isActive = false;
if (!(ServerEndpoint is SteamP2PEndpoint steamIdEndpoint))
{
throw new InvalidCastException("endPoint is not SteamId");
}
hostSteamId = steamIdEndpoint.SteamId;
}
@@ -55,16 +56,13 @@ namespace Barotrauma.Networking
ServerConnection = new SteamP2PConnection(hostSteamId);
ServerConnection.SetAccountInfo(new AccountInfo(hostSteamId));
incomingInitializationMessages = new List<IReadMessage>();
incomingDataMessages = new List<IReadMessage>();
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write((byte)DeliveryMethod.Reliable);
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
outMsg.Write((byte)ConnectionInitialization.ConnectionStarted);
Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable);
sentBytes += outMsg.LengthBytes;
var headers = new PeerPacketHeaders
{
DeliveryMethod = DeliveryMethod.Reliable,
PacketHeader = PacketHeader.IsConnectionInitializationStep,
Initialization = ConnectionInitialization.ConnectionStarted
};
SendMsgInternal(headers, null);
initializationStep = ConnectionInitialization.SteamTicketAndVersion;
@@ -78,6 +76,7 @@ namespace Barotrauma.Networking
private void OnIncomingConnection(Steamworks.SteamId steamId)
{
if (!isActive) { return; }
if (steamId == hostSteamId.Value)
{
Steamworks.SteamNetworking.AcceptP2PSessionWithUser(steamId);
@@ -86,16 +85,18 @@ namespace Barotrauma.Networking
initializationStep != ConnectionInitialization.ContentPackageOrder &&
initializationStep != ConnectionInitialization.Success)
{
DebugConsole.ThrowError($"Connection from incorrect SteamID was rejected: "+
$"expected {hostSteamId}," +
$"got {new SteamId(steamId)}");
DebugConsole.ThrowError("Connection from incorrect SteamID was rejected: " +
$"expected {hostSteamId}," +
$"got {new SteamId(steamId)}");
}
}
private void OnConnectionFailed(Steamworks.SteamId steamId, Steamworks.P2PSessionError error)
{
if (!isActive) { return; }
if (steamId != hostSteamId.Value) { return; }
Close($"SteamP2P connection failed: {error}");
callbacks.OnDisconnectMessageReceived.Invoke($"{DisconnectReason.SteamP2PError}/SteamP2P connection failed: {error}");
}
@@ -103,27 +104,31 @@ namespace Barotrauma.Networking
private void OnP2PData(ulong steamId, byte[] data, int dataLength)
{
if (!isActive) { return; }
if (steamId != hostSteamId.Value) { return; }
timeout = Screen.Selected == GameMain.GameScreen ?
NetworkConnection.TimeoutThresholdInGame :
NetworkConnection.TimeoutThreshold;
PacketHeader packetHeader = (PacketHeader)data[0];
timeout = Screen.Selected == GameMain.GameScreen
? NetworkConnection.TimeoutThresholdInGame
: NetworkConnection.TimeoutThreshold;
IReadMessage inc = new ReadOnlyMessage(data, false, 0, dataLength, ServerConnection);
var (deliveryMethod, packetHeader, initialization) = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
if (!packetHeader.IsServerMessage()) { return; }
if (packetHeader.IsConnectionInitializationStep())
if (packetHeader.IsConnectionInitializationStep() && initialization.HasValue)
{
ulong low = Lidgren.Network.NetBitWriter.ReadUInt32(data, 32, 8);
ulong high = Lidgren.Network.NetBitWriter.ReadUInt32(data, 32, 8 + 32);
ulong lobbyId = low + (high << 32);
var relayPacket = INetSerializableStruct.Read<SteamP2PInitializationRelayPacket>(inc);
Steam.SteamManager.JoinLobby(lobbyId, false);
IReadMessage inc = new ReadOnlyMessage(data, false, 1 + 8, dataLength - (1 + 8), ServerConnection);
SteamManager.JoinLobby(relayPacket.LobbyID, false);
if (initializationStep != ConnectionInitialization.Success)
{
incomingInitializationMessages.Add(inc);
incomingInitializationMessages.Add(new IncomingInitializationMessage
{
InitializationStep = initialization.Value,
Message = relayPacket.Message.GetReadMessage()
});
}
}
else if (packetHeader.IsHeartbeatMessage())
@@ -132,17 +137,14 @@ namespace Barotrauma.Networking
}
else if (packetHeader.IsDisconnectMessage())
{
IReadMessage inc = new ReadOnlyMessage(data, false, 1, dataLength - 1, ServerConnection);
string msg = inc.ReadString();
Close(msg);
callbacks.OnDisconnectMessageReceived.Invoke(msg);
PeerDisconnectPacket packet = INetSerializableStruct.Read<PeerDisconnectPacket>(inc);
Close(packet.Message);
callbacks.OnDisconnectMessageReceived.Invoke(packet.Message);
}
else
{
UInt16 length = Lidgren.Network.NetBitWriter.ReadUInt16(data, 16, 8);
IReadMessage inc = new ReadOnlyMessage(data, packetHeader.IsCompressed(), 3, length, ServerConnection);
incomingDataMessages.Add(inc);
var packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
incomingDataMessages.Add(packet.GetReadMessage());
}
}
@@ -154,6 +156,7 @@ namespace Barotrauma.Networking
{
timeout -= deltaTime;
}
heartbeatTimer -= deltaTime;
if (initializationStep != ConnectionInitialization.Password &&
@@ -163,20 +166,20 @@ namespace Barotrauma.Networking
connectionStatusTimer -= deltaTime;
if (connectionStatusTimer <= 0.0)
{
var state = Steamworks.SteamNetworking.GetP2PSessionState(hostSteamId.Value);
if (state == null)
if (Steamworks.SteamNetworking.GetP2PSessionState(hostSteamId.Value) is { } state)
{
if (state.P2PSessionError != Steamworks.P2PSessionError.None)
{
Close($"SteamP2P error code: {state.P2PSessionError}");
callbacks.OnDisconnectMessageReceived.Invoke($"{DisconnectReason.SteamP2PError}/SteamP2P error code: {state.P2PSessionError}");
}
}
else
{
Close("SteamP2P connection could not be established");
callbacks.OnDisconnectMessageReceived.Invoke(DisconnectReason.SteamP2PError.ToString());
}
else
{
if (state?.P2PSessionError != Steamworks.P2PSessionError.None)
{
Close($"SteamP2P error code: {state?.P2PSessionError}");
callbacks.OnDisconnectMessageReceived.Invoke($"{DisconnectReason.SteamP2PError}/SteamP2P error code: {state?.P2PSessionError}");
}
}
connectionStatusTimer = 1.0f;
}
}
@@ -184,11 +187,12 @@ namespace Barotrauma.Networking
for (int i = 0; i < 100; i++)
{
if (!Steamworks.SteamNetworking.IsP2PPacketAvailable()) { break; }
var packet = Steamworks.SteamNetworking.ReadP2PPacket();
if (packet.HasValue)
if (packet is { SteamId: var steamId, Data: var data })
{
OnP2PData(packet?.SteamId ?? 0, packet?.Data, packet?.Data.Length ?? 0);
receivedBytes += packet?.Data.Length ?? 0;
OnP2PData(steamId, data, data.Length);
receivedBytes += data.Length;
}
}
@@ -197,14 +201,13 @@ namespace Barotrauma.Networking
if (heartbeatTimer < 0.0)
{
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write((byte)DeliveryMethod.Unreliable);
outMsg.Write((byte)PacketHeader.IsHeartbeatMessage);
Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Unreliable);
sentBytes += outMsg.LengthBytes;
heartbeatTimer = 5.0;
var headers = new PeerPacketHeaders
{
DeliveryMethod = DeliveryMethod.Unreliable,
PacketHeader = PacketHeader.IsHeartbeatMessage,
Initialization = null
};
SendMsgInternal(headers, null);
}
if (timeout < 0.0)
@@ -238,7 +241,7 @@ namespace Barotrauma.Networking
}
else
{
foreach (IReadMessage inc in incomingInitializationMessages)
foreach (var inc in incomingInitializationMessages)
{
ReadConnectionInitializationStep(inc);
}
@@ -261,76 +264,40 @@ namespace Barotrauma.Networking
{
if (!isActive) { return; }
byte[] buf = new byte[msg.LengthBytes + 4];
buf[0] = (byte)deliveryMethod;
byte[] bufAux = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _);
byte[] bufAux = new byte[msg.LengthBytes];
msg.PrepareForSending(ref bufAux, compressPastThreshold, out bool isCompressed, out int length);
buf[1] = (byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None);
buf[2] = (byte)(length & 0xff);
buf[3] = (byte)((length >> 8) & 0xff);
Array.Copy(bufAux, 0, buf, 4, length);
Steamworks.P2PSend sendType;
switch (deliveryMethod)
var headers = new PeerPacketHeaders
{
case DeliveryMethod.Reliable:
case DeliveryMethod.ReliableOrdered:
//the documentation seems to suggest that the Reliable send type
//enforces packet order (TODO: verify)
sendType = Steamworks.P2PSend.Reliable;
break;
default:
sendType = Steamworks.P2PSend.Unreliable;
break;
}
if (length + 8 >= MsgConstants.MTU)
DeliveryMethod = deliveryMethod,
PacketHeader = isCompressed ? PacketHeader.IsCompressed : PacketHeader.None,
Initialization = null
};
var body = new PeerPacketMessage
{
DebugConsole.Log("WARNING: message length comes close to exceeding MTU, forcing reliable send (" + length.ToString() + " bytes)");
sendType = Steamworks.P2PSend.Reliable;
}
Buffer = bufAux
};
heartbeatTimer = 5.0;
// Using an extra local method here to reduce chance of error whenever we need to change this
void performSend() => SendMsgInternal(headers, body);
#if DEBUG
CoroutineManager.Invoke(() =>
{
if (GameMain.Client == null) { return; }
if (Rand.Range(0.0f, 1.0f) < GameMain.Client.SimulatedLoss && sendType != Steamworks.P2PSend.Reliable) { return; }
int count = Rand.Range(0.0f, 1.0f) < GameMain.Client.SimulatedDuplicatesChance ? 2 : 1;
for (int i = 0; i < count; i++)
{
Send(buf, length + 4, sendType);
}
},
GameMain.Client.SimulatedMinimumLatency + Rand.Range(0.0f, GameMain.Client.SimulatedRandomLatency));
#else
Send(buf, length + 4, sendType);
#endif
}
if (GameMain.Client == null) { return; }
private void Send(byte[] buf, int length, Steamworks.P2PSend sendType)
{
bool successSend = Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, buf, length + 4, 0, sendType);
sentBytes += length + 4;
if (!successSend)
{
if (sendType != Steamworks.P2PSend.Reliable)
{
DebugConsole.Log("WARNING: message couldn't be sent unreliably, forcing reliable send (" + length.ToString() + " bytes)");
sendType = Steamworks.P2PSend.Reliable;
successSend = Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, buf, length + 4, 0, sendType);
sentBytes += length + 4;
}
if (!successSend)
{
DebugConsole.AddWarning("Failed to send message to remote peer! (" + length.ToString() + " bytes)");
}
}
if (Rand.Range(0.0f, 1.0f) < GameMain.Client.SimulatedLoss && deliveryMethod is DeliveryMethod.Unreliable) { return; }
int count = Rand.Range(0.0f, 1.0f) < GameMain.Client.SimulatedDuplicatesChance ? 2 : 1;
for (int i = 0; i < count; i++)
{
performSend();
}
},
GameMain.Client.SimulatedMinimumLatency + Rand.Range(0.0f, GameMain.Client.SimulatedRandomLatency));
#else
performSend();
#endif
}
public override void SendPassword(string password)
@@ -338,20 +305,22 @@ namespace Barotrauma.Networking
if (!isActive) { return; }
if (initializationStep != ConnectionInitialization.Password) { return; }
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write((byte)DeliveryMethod.Reliable);
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
outMsg.Write((byte)ConnectionInitialization.Password);
byte[] saltedPw = ServerSettings.SaltPassword(Encoding.UTF8.GetBytes(password), passwordSalt);
outMsg.Write((byte)saltedPw.Length);
outMsg.Write(saltedPw, 0, saltedPw.Length);
heartbeatTimer = 5.0;
Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable);
sentBytes += outMsg.LengthBytes;
var headers = new PeerPacketHeaders
{
DeliveryMethod = DeliveryMethod.Reliable,
PacketHeader = PacketHeader.IsConnectionInitializationStep,
Initialization = ConnectionInitialization.Password
};
var body = new ClientPeerPasswordPacket
{
Password = ServerSettings.SaltPassword(Encoding.UTF8.GetBytes(password), passwordSalt)
};
SendMsgInternal(headers, body);
}
public override void Close(string msg = null, bool disableReconnect = false)
public override void Close(string? msg = null, bool disableReconnect = false)
{
if (!isActive) { return; }
@@ -359,54 +328,59 @@ namespace Barotrauma.Networking
isActive = false;
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write((byte)DeliveryMethod.Reliable);
outMsg.Write((byte)PacketHeader.IsDisconnectMessage);
outMsg.Write(msg ?? "Disconnected");
var headers = new PeerPacketHeaders
{
DeliveryMethod = DeliveryMethod.Reliable,
PacketHeader = PacketHeader.IsDisconnectMessage,
Initialization = null
};
var body = new PeerDisconnectPacket
{
Message = msg ?? "Disconnected"
};
try
{
Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable);
sentBytes += outMsg.LengthBytes;
}
catch (Exception e)
{
DebugConsole.ThrowError("Failed to send a disconnect message to the server using SteamP2P.", e);
}
SendMsgInternal(headers, body);
Thread.Sleep(100);
Steamworks.SteamNetworking.ResetActions();
Steamworks.SteamNetworking.CloseP2PSessionWithUser(hostSteamId.Value);
steamAuthTicket?.Cancel(); steamAuthTicket = null;
steamAuthTicket?.Cancel();
steamAuthTicket = null;
callbacks.OnDisconnect.Invoke(disableReconnect);
}
protected override void SendMsgInternal(DeliveryMethod deliveryMethod, IWriteMessage msg)
protected override void SendMsgInternal(PeerPacketHeaders headers, INetSerializableStruct? body)
{
Steamworks.P2PSend sendType;
switch (deliveryMethod)
IWriteMessage msgToSend = new WriteOnlyMessage();
msgToSend.WriteNetSerializableStruct(headers);
body?.Write(msgToSend);
ForwardToSteamP2P(msgToSend, headers.DeliveryMethod);
}
private void ForwardToSteamP2P(IWriteMessage msg, DeliveryMethod deliveryMethod)
{
heartbeatTimer = 5.0;
int length = msg.LengthBytes;
bool successSend = Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, msg.Buffer, length, 0, deliveryMethod.ToSteam());
sentBytes += length;
if (successSend) { return; }
if (deliveryMethod is DeliveryMethod.Unreliable)
{
case DeliveryMethod.Reliable:
case DeliveryMethod.ReliableOrdered:
//the documentation seems to suggest that the Reliable send type
//enforces packet order (TODO: verify)
sendType = Steamworks.P2PSend.Reliable;
break;
default:
sendType = Steamworks.P2PSend.Unreliable;
break;
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());
sentBytes += length;
}
IWriteMessage msgToSend = new WriteOnlyMessage();
msgToSend.Write((byte)deliveryMethod);
msgToSend.Write(msg.Buffer, 0, msg.LengthBytes);
heartbeatTimer = 5.0;
Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, msgToSend.Buffer, msgToSend.LengthBytes, 0, sendType);
sentBytes += msg.LengthBytes;
if (!successSend)
{
DebugConsole.AddWarning($"Failed to send message to remote peer! ({length} bytes)");
}
}
#if DEBUG
@@ -416,4 +390,4 @@ namespace Barotrauma.Networking
}
#endif
}
}
}
@@ -1,38 +1,46 @@
using Barotrauma.Extensions;
#nullable enable
using Barotrauma.Steam;
using System;
using System.Collections.Generic;
using System.Threading;
using Barotrauma.Extensions;
namespace Barotrauma.Networking
{
class SteamP2POwnerPeer : ClientPeer
sealed class SteamP2POwnerPeer : ClientPeer
{
private bool isActive;
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.Write(val.Value ^ ownerKey64);
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;
class RemotePeer
private sealed class RemotePeer
{
public SteamId SteamId;
public readonly SteamId SteamId;
public Option<SteamId> OwnerSteamId;
public double? DisconnectTime;
public bool Authenticating;
public bool Authenticated;
public class UnauthedMessage
public readonly struct UnauthedMessage
{
public DeliveryMethod DeliveryMethod;
public IWriteMessage Message;
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)
@@ -45,9 +53,9 @@ namespace Barotrauma.Networking
UnauthedMessages = new List<UnauthedMessage>();
}
}
List<RemotePeer> remotePeers;
private List<RemotePeer> remotePeers = null!;
public SteamP2POwnerPeer(Callbacks callbacks, int ownerKey) : base(new PipeEndpoint(), callbacks, Option<int>.Some(ownerKey))
{
@@ -84,8 +92,8 @@ namespace Barotrauma.Networking
private void OnAuthChange(Steamworks.SteamId steamId, Steamworks.SteamId ownerId, Steamworks.AuthResponse status)
{
RemotePeer remotePeer = remotePeers.Find(p => p.SteamId.Value == steamId);
DebugConsole.Log(steamId + " validation: " + status + ", " + (remotePeer != null));
RemotePeer? remotePeer = remotePeers.Find(p => p.SteamId.Value == steamId);
DebugConsole.Log($"{steamId} validation: {status}, {remotePeer != null}");
if (remotePeer == null) { return; }
@@ -93,36 +101,32 @@ namespace Barotrauma.Networking
{
if (status != Steamworks.AuthResponse.OK)
{
DisconnectPeer(remotePeer, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam authentication status changed: " + status.ToString());
DisconnectPeer(remotePeer, $"{DisconnectReason.SteamAuthenticationFailed}/ Steam authentication status changed: {status}");
}
return;
}
if (status == Steamworks.AuthResponse.OK)
{
remotePeer.OwnerSteamId = Option<SteamId>.Some(new SteamId(ownerId));
SteamId ownerSteamId = new SteamId(ownerId);
remotePeer.OwnerSteamId = Option<SteamId>.Some(ownerSteamId);
remotePeer.Authenticated = true;
remotePeer.Authenticating = false;
foreach (var msg in remotePeer.UnauthedMessages)
foreach (var unauthedMessage in remotePeer.UnauthedMessages)
{
//rewrite the owner id before
//forwarding the messages to
//the server, since it's only
//known now
int prevBitPosition = msg.Message.BitPosition;
msg.Message.BitPosition = sizeof(ulong) * 8;
WriteSteamId(msg.Message, new SteamId(ownerId));
msg.Message.BitPosition = prevBitPosition;
byte[] msgToSend = (byte[])msg.Message.Buffer.Clone();
Array.Resize(ref msgToSend, msg.Message.LengthBytes);
ChildServerRelay.Write(msgToSend);
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, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam authentication failed: " + status.ToString());
return;
DisconnectPeer(remotePeer, $"{DisconnectReason.SteamAuthenticationFailed}/ Steam authentication failed: {status}");
}
}
@@ -138,58 +142,54 @@ namespace Barotrauma.Networking
Steamworks.SteamNetworking.AcceptP2PSessionWithUser(steamId); //accept all connections, the server will figure things out later
}
private void OnP2PData(ulong steamId, byte[] data, int dataLength, int _)
private void OnP2PData(ulong steamId, IReadMessage inc)
{
if (!isActive) { return; }
RemotePeer remotePeer = remotePeers.Find(p => p.SteamId.Value == steamId);
RemotePeer? remotePeer = remotePeers.Find(p => p.SteamId.Value == steamId);
if (remotePeer == null) { return; }
if (remotePeer.DisconnectTime != null) { return; }
IWriteMessage outMsg = new WriteOnlyMessage();
var steamUserId = new SteamId(steamId);
WriteSteamId(outMsg, steamUserId);
WriteSteamId(outMsg, remotePeer.OwnerSteamId.Fallback(steamUserId));
outMsg.Write(data, 1, dataLength - 1);
DeliveryMethod deliveryMethod = (DeliveryMethod)data[0];
PacketHeader packetHeader = (PacketHeader)data[1];
var peerPacketHeaders = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
PacketHeader packetHeader = peerPacketHeaders.PacketHeader;
if (!remotePeer.Authenticated && !remotePeer.Authenticating && packetHeader.IsConnectionInitializationStep())
{
remotePeer.DisconnectTime = null;
IReadMessage authMsg = new ReadOnlyMessage(data, packetHeader.IsCompressed(), 2, dataLength - 2, null);
ConnectionInitialization initializationStep = (ConnectionInitialization)authMsg.ReadByte();
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion)
ConnectionInitialization initialization = peerPacketHeaders.Initialization ?? throw new Exception("Initialization step missing");
if (initialization == ConnectionInitialization.SteamTicketAndVersion)
{
remotePeer.Authenticating = true;
authMsg.ReadString(); //skip name
authMsg.ReadInt32(); //skip owner key
authMsg.ReadUInt64(); //skip steamid
UInt16 ticketLength = authMsg.ReadUInt16();
byte[] ticket = authMsg.ReadBytes(ticketLength);
Steamworks.BeginAuthResult authSessionStartState = Steam.SteamManager.StartAuthSession(ticket, steamId);
var packet = INetSerializableStruct.Read<ClientSteamTicketAndVersionPacket>(inc);
packet.SteamAuthTicket.TryUnwrap(out byte[] ticket);
Steamworks.BeginAuthResult authSessionStartState = SteamManager.StartAuthSession(ticket, steamId);
if (authSessionStartState != Steamworks.BeginAuthResult.OK)
{
DisconnectPeer(remotePeer, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam auth session failed to start: " + authSessionStartState.ToString());
DisconnectPeer(remotePeer, $"{DisconnectReason.SteamAuthenticationFailed}/ Steam auth session failed to start: {authSessionStartState}");
return;
}
}
}
var steamUserId = new SteamId(steamId);
if (remotePeer.Authenticating)
{
remotePeer.UnauthedMessages.Add(new RemotePeer.UnauthedMessage() { DeliveryMethod = deliveryMethod, Message = outMsg });
remotePeer.UnauthedMessages.Add(new RemotePeer.UnauthedMessage(steamUserId, inc.Buffer));
}
else
{
byte[] msgToSend = (byte[])outMsg.Buffer.Clone();
Array.Resize(ref msgToSend, outMsg.LengthBytes);
ChildServerRelay.Write(msgToSend);
IWriteMessage outMsg = new WriteOnlyMessage();
WriteSteamId(outMsg, steamUserId);
WriteSteamId(outMsg, remotePeer.OwnerSteamId.Fallback(steamUserId));
outMsg.WriteBytes(inc.Buffer, 0, inc.LengthBytes);
ForwardToServerProcess(outMsg);
}
}
@@ -201,7 +201,11 @@ namespace Barotrauma.Networking
{
Close();
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage);
msgBox.Buttons[0].OnClicked += (btn, obj) => { GameMain.MainMenuScreen.Select(); return false; };
msgBox.Buttons[0].OnClicked += (btn, obj) =>
{
GameMain.MainMenuScreen.Select();
return false;
};
return;
}
@@ -216,11 +220,12 @@ namespace Barotrauma.Networking
for (int i = 0; i < 100; i++)
{
if (!Steamworks.SteamNetworking.IsP2PPacketAvailable()) { break; }
var packet = Steamworks.SteamNetworking.ReadP2PPacket();
if (packet.HasValue)
if (packet is { SteamId: var steamId, Data: var data })
{
OnP2PData(packet?.SteamId ?? 0, packet?.Data, packet?.Data.Length ?? 0, 0);
receivedBytes += packet?.Data.Length ?? 0;
OnP2PData(steamId, new ReadWriteMessage(data, 0, data.Length * 8, false));
receivedBytes += data.Length;
}
}
@@ -240,155 +245,144 @@ namespace Barotrauma.Networking
if (!isActive) { return; }
SteamId recipientSteamId = ReadSteamId(inc);
DeliveryMethod deliveryMethod = (DeliveryMethod)inc.ReadByte();
int p2pDataStart = inc.BytePosition;
PacketHeader packetHeader = (PacketHeader)inc.ReadByte();
var peerPacketHeaders = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
if (recipientSteamId != selfSteamID)
{
if (!packetHeader.IsServerMessage())
{
DebugConsole.ThrowError("Received non-server message meant for remote peer");
return;
}
RemotePeer peer = remotePeers.Find(p => p.SteamId == recipientSteamId);
if (peer == null) { return; }
if (packetHeader.IsDisconnectMessage())
{
DisconnectPeer(peer, inc.ReadString());
return;
}
Steamworks.P2PSend sendType;
switch (deliveryMethod)
{
case DeliveryMethod.Reliable:
case DeliveryMethod.ReliableOrdered:
//the documentation seems to suggest that the
//Reliable send type enforces packet order
sendType = Steamworks.P2PSend.Reliable;
break;
default:
sendType = Steamworks.P2PSend.Unreliable;
break;
}
byte[] p2pData;
if (packetHeader.IsConnectionInitializationStep())
{
p2pData = new byte[inc.LengthBytes - p2pDataStart + 8];
p2pData[0] = inc.Buffer[p2pDataStart];
Lidgren.Network.NetBitWriter.WriteUInt64(SteamManager.CurrentLobbyID, 8 * 8, p2pData, 1 * 8);
Array.Copy(inc.Buffer, p2pDataStart+1, p2pData, 1 + 8, inc.LengthBytes - p2pDataStart - 1);
}
else
{
p2pData = new byte[inc.LengthBytes - p2pDataStart];
Array.Copy(inc.Buffer, p2pDataStart, p2pData, 0, p2pData.Length);
if (!packetHeader.IsHeartbeatMessage() && !packetHeader.IsDisconnectMessage())
{
UInt16 length = Lidgren.Network.NetBitWriter.ReadUInt16(p2pData, 16, 8);
if (length > p2pData.Length - 2)
{
string errorMsg = $"Length written in message to send to client is larger than buffer size ({length} > {p2pData.Length - 2})";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce(
"SteamP2POwnerPeerLengthValidationFail",
GameAnalyticsManager.ErrorSeverity.Error,
errorMsg);
}
}
}
if (p2pData.Length + 4 >= MsgConstants.MTU)
{
DebugConsole.Log("WARNING: message length comes close to exceeding MTU, forcing reliable send (" + p2pData.Length.ToString() + " bytes)");
sendType = Steamworks.P2PSend.Reliable;
}
bool successSend = Steamworks.SteamNetworking.SendP2PPacket(recipientSteamId.Value, p2pData, p2pData.Length, 0, sendType);
sentBytes += p2pData.Length;
if (!successSend)
{
if (sendType != Steamworks.P2PSend.Reliable)
{
DebugConsole.Log("WARNING: message couldn't be sent unreliably, forcing reliable send (" + p2pData.Length.ToString() + " bytes)");
sendType = Steamworks.P2PSend.Reliable;
successSend = Steamworks.SteamNetworking.SendP2PPacket(recipientSteamId.Value, p2pData, p2pData.Length, 0, sendType);
sentBytes += p2pData.Length;
}
if (!successSend)
{
DebugConsole.AddWarning("Failed to send message to remote peer! (" + p2pData.Length.ToString() + " bytes)");
}
}
HandleMessageForRemotePeer(peerPacketHeaders, recipientSteamId, inc);
}
else
{
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.Write((byte)(PacketHeader.IsConnectionInitializationStep));
outMsg.Write(GameMain.Client.Name);
byte[] msgToSend = (byte[])outMsg.Buffer.Clone();
Array.Resize(ref msgToSend, outMsg.LengthBytes);
ChildServerRelay.Write(msgToSend);
return;
}
else
{
if (initializationStep != ConnectionInitialization.Success)
{
callbacks.OnInitializationComplete.Invoke();
initializationStep = ConnectionInitialization.Success;
}
UInt16 length = inc.ReadUInt16();
IReadMessage msg = new ReadOnlyMessage(inc.Buffer, packetHeader.IsCompressed(), inc.BytePosition, length, ServerConnection);
callbacks.OnMessageReceived.Invoke(msg);
return;
}
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.Message);
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, string msg)
{
if (!string.IsNullOrWhiteSpace(msg))
{
if (peer.DisconnectTime == null)
{
peer.DisconnectTime = Timing.TotalTime + 1.0;
}
peer.DisconnectTime ??= Timing.TotalTime + 1.0;
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write((byte)(PacketHeader.IsServerMessage | PacketHeader.IsDisconnectMessage));
outMsg.Write(msg);
outMsg.WriteNetSerializableStruct(new PeerPacketHeaders
{
DeliveryMethod = DeliveryMethod.Reliable,
PacketHeader = PacketHeader.IsServerMessage | PacketHeader.IsDisconnectMessage
});
outMsg.WriteNetSerializableStruct(new PeerDisconnectPacket
{
Message = msg
});
Steamworks.SteamNetworking.SendP2PPacket(peer.SteamId.Value, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable);
sentBytes += outMsg.LengthBytes;
}
@@ -406,10 +400,10 @@ namespace Barotrauma.Networking
public override void SendPassword(string password)
{
return; //owner doesn't send passwords
//owner doesn't send passwords
}
public override void Close(string msg = null, bool disableReconnect = false)
public override void Close(string? msg = null, bool disableReconnect = false)
{
if (!isActive) { return; }
@@ -441,25 +435,62 @@ namespace Barotrauma.Networking
if (!isActive) { return; }
IWriteMessage msgToSend = new WriteOnlyMessage();
byte[] msgData = new byte[msg.LengthBytes];
msg.PrepareForSending(ref msgData, compressPastThreshold, out bool isCompressed, out int length);
byte[] msgData = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _);
WriteSteamId(msgToSend, selfSteamID);
WriteSteamId(msgToSend, selfSteamID);
msgToSend.Write((byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None));
msgToSend.Write((UInt16)length);
msgToSend.Write(msgData, 0, length);
byte[] bufToSend = (byte[])msgToSend.Buffer.Clone();
Array.Resize(ref bufToSend, msgToSend.LengthBytes);
ChildServerRelay.Write(bufToSend);
msgToSend.WriteNetSerializableStruct(new PeerPacketHeaders
{
DeliveryMethod = deliveryMethod,
PacketHeader = isCompressed ? PacketHeader.IsCompressed : PacketHeader.None
});
msgToSend.WriteNetSerializableStruct(new PeerPacketMessage
{
Buffer = msgData
});
ForwardToServerProcess(msgToSend);
}
protected override void SendMsgInternal(DeliveryMethod deliveryMethod, IWriteMessage msg)
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()
{
@@ -467,4 +498,4 @@ namespace Barotrauma.Networking
}
#endif
}
}
}