Release 1.10.5.0 - Autumn Update 2025

This commit is contained in:
Regalis11
2025-09-17 13:44:21 +03:00
parent d13836ce87
commit caa0326cf8
120 changed files with 2584 additions and 635 deletions
@@ -256,6 +256,15 @@ namespace Barotrauma.Networking
}
string downloadFolder = downloadFolders[(FileTransferType)fileType];
#if CLIENT && DEBUG
if (GameClient.MultiClientTestMode)
{
//append the name of the client to the download folder to avoid multiple clients
//from trying to download a file into the same path at the same time
downloadFolder += "_" + GameMain.Client.Name;
}
#endif
if (!Directory.Exists(downloadFolder))
{
try
@@ -11,7 +11,6 @@ using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using System.Xml.Linq;
using Barotrauma.PerkBehaviors;
namespace Barotrauma.Networking
{
@@ -26,6 +25,8 @@ namespace Barotrauma.Networking
#if DEBUG
public float DebugServerVoipAmplitude;
public static bool MultiClientTestMode;
#endif
public override Voting Voting { get; }
@@ -873,8 +874,9 @@ namespace Barotrauma.Networking
ReadAchievement(inc);
break;
case ServerPacketHeader.UNLOCKRECIPE:
CharacterTeamType team = (CharacterTeamType)inc.ReadByte();
Identifier identifier = inc.ReadIdentifier();
GameMain.GameSession.UnlockRecipe(identifier, showNotifications: true);
GameMain.GameSession?.UnlockRecipe(team, identifier, showNotifications: true);
break;
case ServerPacketHeader.ACHIEVEMENT_STAT:
ReadAchievementStat(inc);
@@ -10,30 +10,31 @@ sealed class DualStackP2PSocket : P2PSocket
private DualStackP2PSocket(
Callbacks callbacks,
Option<EosP2PSocket> eosSocket,
Option<SteamListenSocket> steamSocket) :
base(callbacks)
Option<SteamListenSocket> steamSocket,
OwnerOrClient type) :
base(callbacks, type)
{
this.eosSocket = eosSocket;
this.steamSocket = steamSocket;
}
public static Result<P2PSocket, Error> Create(Callbacks callbacks)
public static Result<P2PSocket, Error> Create(Callbacks callbacks, OwnerOrClient type)
{
var eosP2PSocketResult = EosP2PSocket.Create(callbacks);
var steamP2PSocketResult = SteamListenSocket.Create(callbacks);
var eosP2PSocketResult = EosP2PSocket.Create(callbacks, type);
var steamP2PSocketResult = SteamListenSocket.Create(callbacks, type);
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));
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, type));
}
public override void ProcessIncomingMessages()
@@ -8,13 +8,14 @@ sealed class EosP2PSocket : P2PSocket
private EosP2PSocket(
Callbacks callbacks,
EosInterface.P2PSocket eosSocket)
: base(callbacks)
EosInterface.P2PSocket eosSocket,
OwnerOrClient type)
: base(callbacks, type)
{
this.eosSocket = eosSocket;
}
public static Result<P2PSocket, Error> Create(Callbacks callbacks)
public static Result<P2PSocket, Error> Create(Callbacks callbacks, OwnerOrClient type)
{
if (!EosInterface.Core.IsInitialized) { return Result.Failure(new Error(ErrorCode.EosNotInitialized)); }
@@ -26,19 +27,25 @@ sealed class EosP2PSocket : P2PSocket
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);
var retVal = new EosP2PSocket(callbacks, eosSocket, type);
eosSocket.HandleIncomingConnection.Register("Event".ToIdentifier(), retVal.OnIncomingConnection);
eosSocket.HandleClosedConnection.Register("Event".ToIdentifier(), retVal.OnConnectionClosed);
return Result.Success((P2PSocket)retVal);
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));
EosP2PEndpoint endpoint = new EosP2PEndpoint(msg.Sender);
callbacks.OnData(endpoint, new ReadWriteMessage(msg.Buffer, 0, msg.ByteLength * 8, false));
if (Type is OwnerOrClient.Owner)
{
dosProtection.OnPacket(endpoint);
}
}
}
@@ -8,6 +8,15 @@ namespace Barotrauma.Networking;
abstract class P2PSocket : IDisposable
{
public readonly P2POwnerDoSProtection dosProtection;
public readonly OwnerOrClient Type;
public enum OwnerOrClient
{
Client,
Owner
}
public enum ErrorCode
{
EosNotInitialized,
@@ -38,12 +47,16 @@ abstract class P2PSocket : IDisposable
public readonly record struct Callbacks(
Predicate<P2PEndpoint> OnIncomingConnection,
Action<P2PEndpoint, PeerDisconnectPacket> OnConnectionClosed,
P2POwnerDoSProtection.ExcessivePacketDelegate OnExcessivePackets,
Action<P2PEndpoint, IReadMessage> OnData);
protected readonly Callbacks callbacks;
protected P2PSocket(Callbacks callbacks)
protected P2PSocket(Callbacks callbacks, OwnerOrClient type)
{
this.callbacks = callbacks;
Type = type;
dosProtection = new P2POwnerDoSProtection(callbacks.OnExcessivePackets);
}
public abstract void ProcessIncomingMessages();
@@ -64,13 +64,13 @@ sealed class SteamConnectSocket : P2PSocket
private readonly SteamP2PEndpoint expectedEndpoint;
private readonly ConnectionManager connectionManager;
private SteamConnectSocket(SteamP2PEndpoint expectedEndpoint, Callbacks callbacks, ConnectionManager connectionManager) : base(callbacks)
private SteamConnectSocket(SteamP2PEndpoint expectedEndpoint, Callbacks callbacks, ConnectionManager connectionManager, OwnerOrClient type) : base(callbacks, type)
{
this.expectedEndpoint = expectedEndpoint;
this.connectionManager = connectionManager;
}
public static Result<P2PSocket, Error> Create(SteamP2PEndpoint endpoint, Callbacks callbacks)
public static Result<P2PSocket, Error> Create(SteamP2PEndpoint endpoint, Callbacks callbacks, OwnerOrClient type)
{
if (!SteamManager.IsInitialized) { return Result.Failure(new Error(ErrorCode.SteamNotInitialized)); }
@@ -87,7 +87,7 @@ sealed class SteamConnectSocket : P2PSocket
if (connectionManager is null) { return Result.Failure(new Error(ErrorCode.FailedToCreateSteamP2PSocket)); }
connectionManager.SetEndpointAndCallbacks(endpoint, callbacks);
return Result.Success((P2PSocket)new SteamConnectSocket(endpoint, callbacks, connectionManager));
return Result.Success<P2PSocket>(new SteamConnectSocket(endpoint, callbacks, connectionManager, type));
}
public override void ProcessIncomingMessages()
@@ -10,12 +10,14 @@ sealed class SteamListenSocket : P2PSocket
private sealed class SocketManager : Steamworks.SocketManager, Steamworks.ISocketManager
{
private Callbacks callbacks;
private P2PSocket socket;
private readonly Dictionary<SteamP2PEndpoint, Steamworks.Data.Connection> endpointToConnection = new();
public void SetCallbacks(Callbacks callbacks)
{
this.callbacks = callbacks;
}
=> this.callbacks = callbacks;
public void SetSocket(P2PSocket socket)
=> this.socket = socket;
public override void OnConnecting(Steamworks.Data.Connection connection, Steamworks.Data.ConnectionInfo info)
{
@@ -65,7 +67,7 @@ sealed class SteamListenSocket : P2PSocket
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 || data == IntPtr.Zero) { return; }
@@ -75,6 +77,11 @@ sealed class SteamListenSocket : P2PSocket
Marshal.Copy(source: data, destination: dataArray, startIndex: 0, length: size);
callbacks.OnData(endpoint, new ReadWriteMessage(dataArray, bitPos: 0, lBits: size * 8, copyBuf: false));
if (socket?.Type is OwnerOrClient.Owner)
{
socket.dosProtection.OnPacket(endpoint);
}
}
internal bool SendMessage(SteamP2PEndpoint endpoint, IWriteMessage outMsg, DeliveryMethod deliveryMethod)
@@ -107,26 +114,49 @@ sealed class SteamListenSocket : P2PSocket
private SteamListenSocket(
Callbacks callbacks,
SocketManager socketManager)
: base(callbacks)
SocketManager socketManager,
OwnerOrClient type)
: base(callbacks, type)
{
this.socketManager = socketManager;
}
public static Result<P2PSocket, Error> Create(Callbacks callbacks)
public static Result<P2PSocket, Error> Create(Callbacks callbacks, OwnerOrClient type)
{
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));
socketManager.SetCallbacks(callbacks);
P2PSocket socket = new SteamListenSocket(callbacks, socketManager, type);
socketManager.SetSocket(socket);
return Result.Success(socket);
}
public override void ProcessIncomingMessages()
{
socketManager.Receive();
const int bufferSize = 32;
const int maxIterations = 100;
// could technically cause a stack overflow since the call is recursive,
// use a while loop instead
int iteration;
for (iteration = 0; iteration < maxIterations; iteration++)
{
int received = socketManager.Receive(bufferSize: bufferSize, receiveToEnd: false);
if (received < bufferSize)
{
break;
}
}
if (iteration >= maxIterations)
{
DebugConsole.ThrowError("Steam P2P socket received too many messages in a single frame.");
}
}
public override bool SendMessage(P2PEndpoint endpoint, IWriteMessage outMsg, DeliveryMethod deliveryMethod)
@@ -59,11 +59,11 @@ namespace Barotrauma.Networking
ServerConnection = ServerEndpoint.MakeConnectionFromEndpoint();
var socketCallbacks = new P2PSocket.Callbacks(OnIncomingConnection, OnConnectionClosed, OnP2PData);
var socketCallbacks = new P2PSocket.Callbacks(OnIncomingConnection, OnConnectionClosed, OnExcessivePackets, OnP2PData);
var socketCreateResult = ServerEndpoint switch
{
EosP2PEndpoint => EosP2PSocket.Create(socketCallbacks),
SteamP2PEndpoint steamP2PEndpoint => SteamConnectSocket.Create(steamP2PEndpoint, socketCallbacks),
EosP2PEndpoint => EosP2PSocket.Create(socketCallbacks, P2PSocket.OwnerOrClient.Client),
SteamP2PEndpoint steamP2PEndpoint => SteamConnectSocket.Create(steamP2PEndpoint, socketCallbacks, P2PSocket.OwnerOrClient.Client),
_ => throw new Exception($"Invalid server endpoint: {ServerEndpoint.GetType()} {ServerEndpoint}")
};
socket = socketCreateResult.TryUnwrapSuccess(out var s)
@@ -97,6 +97,11 @@ namespace Barotrauma.Networking
isActive = true;
}
private void OnExcessivePackets(P2PEndpoint endpoint, bool shouldBan)
{
// do nothing
}
private bool OnIncomingConnection(P2PEndpoint remoteEndpoint)
{
if (remoteEndpoint == ServerEndpoint)
@@ -163,7 +168,7 @@ namespace Barotrauma.Networking
int completeMessageLengthBits = completeMessage.Length * 8;
incomingDataMessages.Add(new ReadWriteMessage(completeMessage.ToArray(), 0, completeMessageLengthBits, copyBuf: false));
}
else if (packetHeader.IsHeartbeatMessage())
else if (packetHeader.IsHeartbeatMessage() || packetHeader.IsDoSProtectionMessage())
{
return; //TODO: implement heartbeats
}
@@ -88,8 +88,8 @@ namespace Barotrauma.Networking
remotePeers.Clear();
var socketCallbacks = new P2PSocket.Callbacks(OnIncomingConnection, OnConnectionClosed, OnP2PData);
var socketCreateResult = DualStackP2PSocket.Create(socketCallbacks);
var socketCallbacks = new P2PSocket.Callbacks(OnIncomingConnection, OnConnectionClosed, OnExcessivePackets, OnP2PData);
var socketCreateResult = DualStackP2PSocket.Create(socketCallbacks, type: P2PSocket.OwnerOrClient.Owner);
socket = socketCreateResult.TryUnwrapSuccess(out var s)
? s
: throw new Exception($"Failed to create dual-stack socket: {socketCreateResult}");
@@ -187,6 +187,29 @@ namespace Barotrauma.Networking
}
}
private void OnExcessivePackets(P2PEndpoint endpoint, bool shouldBan)
{
IWriteMessage msg = new WriteOnlyMessage();
msg.WriteNetSerializableStruct(new P2POwnerToServerHeader
{
EndpointStr = selfPrimaryEndpoint.StringRepresentation,
AccountInfo = selfAccountInfo
});
msg.WriteNetSerializableStruct(new PeerPacketHeaders
{
DeliveryMethod = DeliveryMethod.Reliable,
PacketHeader = PacketHeader.IsDoSProtectionMessage
});
msg.WriteNetSerializableStruct(new DoSProtectionPacket(endpoint.StringRepresentation, shouldBan));
string dcMsg = TextManager.Get(shouldBan ? "DoSProtectionBanned" : "DoSProtectionKicked")
.Fallback(TextManager.Get("DoSProtectionKicked")).Value;
msg.WriteNetSerializableStruct(shouldBan
? PeerDisconnectPacket.Banned(dcMsg)
: PeerDisconnectPacket.Kicked(dcMsg));
ForwardToServerProcess(msg);
}
private void StartAuthTask(IReadMessage inc, RemotePeer remotePeer)
{
remotePeer.AuthStatus = RemotePeer.AuthenticationStatus.AuthenticationPending;
@@ -612,7 +612,7 @@ namespace Barotrauma.Networking
}
public bool Equals(ServerInfo other)
=> other.Endpoints.Any(e => Endpoints.Contains(e));
=> other != null && other.Endpoints.Any(Endpoints.Contains);
public override int GetHashCode() => Endpoints.First().GetHashCode();
@@ -9,6 +9,8 @@ namespace Barotrauma.Networking
{
public partial class ServerLog
{
const int MaxLines = 500;
public GUIButton LogFrame;
private GUIListBox listBox;
private GUIButton reverseButton;
@@ -17,6 +19,8 @@ namespace Barotrauma.Networking
private bool reverseOrder = false;
private readonly bool[] msgTypeHidden = new bool[Enum.GetValues(typeof(MessageType)).Length];
private bool OnReverseClicked(GUIButton btn, object obj)
{
SetMessageReversal(!reverseOrder);
@@ -105,7 +109,10 @@ namespace Barotrauma.Networking
reverseButton.Children.ForEach(c => c.SpriteEffects = reverseOrder ? SpriteEffects.FlipVertically : SpriteEffects.None);
reverseButton.OnClicked = OnReverseClicked;
listBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.95f), listBoxLayout.RectTransform));
listBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.95f), listBoxLayout.RectTransform))
{
AutoHideScrollBar = false
};
GUIButton closeButton = new GUIButton(new RectTransform(new Vector2(0.25f, 0.05f), rightColumn.RectTransform), TextManager.Get("Close"))
{
@@ -127,7 +134,8 @@ namespace Barotrauma.Networking
listBox.UpdateScrollBarSize();
if (listBox.BarScroll == 0.0f || listBox.BarScroll == 1.0f) { listBox.BarScroll = 1.0f; }
//scrolled all the way down by default
listBox.BarScroll = 1.0f;
msgFilter = "";
}
@@ -189,11 +197,19 @@ namespace Barotrauma.Networking
{
float prevSize = listBox.BarSize;
GUIComponent firstVisibleLine = listBox.Content.Children.FirstOrDefault(c => c.Rect.Y > listBox.Content.Rect.Y);
int firstVisibileYPos = firstVisibleLine?.Rect.Y ?? 0;
while (listBox.Content.CountChildren > MaxLines)
{
listBox.Content.RemoveChild(reverseOrder ? listBox.Content.Children.Last() : listBox.Content.Children.First());
}
GUIFrame textContainer = null;
Anchor anchor = Anchor.TopLeft;
Pivot pivot = Pivot.TopLeft;
RichString richString = line.Text as RichString;
RichString richString = line.Text;
if (richString != null && richString.RichTextData.HasValue)
{
foreach (var data in richString.RichTextData.Value)
@@ -217,7 +233,7 @@ namespace Barotrauma.Networking
line.Text, wrap: true, font: GUIStyle.SmallFont)
{
TextColor = messageColor[line.Type],
Visible = !msgTypeHidden[(int)line.Type],
Visible = !ShouldFilterMessage(line),
CanBeFocused = false,
UserData = line
};
@@ -247,31 +263,47 @@ namespace Barotrauma.Networking
}
}
if ((prevSize == 1.0f && listBox.BarScroll == 0.0f) || (prevSize < 1.0f && listBox.BarScroll == 1.0f)) listBox.BarScroll = 1.0f;
//if the list was scrolled to the bottom, or to the top while the list wasn't full yet,
//keep it scrolled to the bottom
if ((MathUtils.NearlyEqual(prevSize, 1.0f) && MathUtils.NearlyEqual(listBox.BarScroll, 0.0f)) ||
(prevSize < 1.0f && MathUtils.NearlyEqual(listBox.BarScroll, 1.0f)))
{
listBox.BarScroll = 1.0f;
}
//otherwise modify the scroll so the topmost element stays where it was (list doesn't jump as new lines are added when scrolled up)
else if (firstVisibleLine != null)
{
listBox.UpdateScrollBarSize();
listBox.RecalculateChildren();
int diff = firstVisibleLine.Rect.Y - firstVisibileYPos;
if (diff != 0)
{
listBox.BarScroll += diff / listBox.TotalSize * (prevSize / listBox.BarSize);
}
}
}
private bool FilterMessages()
{
string filter = msgFilter == null ? "" : msgFilter.ToLower();
foreach (GUIComponent child in listBox.Content.Children)
{
if (!(child is GUITextBlock textBlock)) { continue; }
if (child is not GUITextBlock) { continue; }
child.Visible = true;
if (msgTypeHidden[(int)((LogMessage)child.UserData).Type])
{
child.Visible = false;
continue;
}
textBlock.Visible = string.IsNullOrEmpty(filter) || textBlock.Text.ToLower().Contains(filter);
child.Visible = !ShouldFilterMessage((LogMessage)child.UserData);
}
listBox.UpdateScrollBarSize();
listBox.BarScroll = 0.0f;
listBox.BarScroll = 1.0f;
return true;
}
private bool ShouldFilterMessage(LogMessage message)
{
if (msgTypeHidden[(int)message.Type]) { return true; }
string text = message.Text.SanitizedValue;
return !string.IsNullOrEmpty(msgFilter) && !text.Contains(msgFilter, StringComparison.InvariantCultureIgnoreCase);
}
private void SetMessageReversal(bool reverse)
{
if (reverseOrder == reverse) { return; }