Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/NetworkPeerStructs.cs
2025-09-17 13:44:21 +03:00

340 lines
14 KiB
C#

#nullable enable
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
namespace Barotrauma.Networking
{
[NetworkSerialize]
internal struct PeerPacketHeaders : INetSerializableStruct
{
public DeliveryMethod DeliveryMethod;
public PacketHeader PacketHeader;
public ConnectionInitialization? Initialization;
public readonly void Deconstruct(
out DeliveryMethod deliveryMethod,
out PacketHeader packetHeader,
out ConnectionInitialization? initialization)
{
deliveryMethod = DeliveryMethod;
packetHeader = PacketHeader;
initialization = Initialization;
}
}
[NetworkSerialize(ArrayMaxSize = ushort.MaxValue)]
internal struct ClientAuthTicketAndVersionPacket : INetSerializableStruct
{
public string Name;
public Option<int> OwnerKey;
public Option<AccountId> AccountId;
public Option<AuthenticationTicket> AuthTicket;
public string GameVersion;
public Identifier Language;
}
[NetworkSerialize]
internal readonly record struct P2POwnerToServerHeader
(string? EndpointStr, AccountInfo AccountInfo) : INetSerializableStruct
{
public Option<P2PEndpoint> Endpoint => P2PEndpoint.Parse(EndpointStr ?? "");
}
[NetworkSerialize]
internal readonly record struct P2PServerToOwnerHeader
(string? EndpointStr) : INetSerializableStruct
{
public Option<P2PEndpoint> Endpoint => P2PEndpoint.Parse(EndpointStr ?? "");
}
[NetworkSerialize]
internal struct P2PInitializationRelayPacket : INetSerializableStruct
{
public ulong LobbyID;
public PeerPacketMessage Message;
}
[NetworkSerialize]
internal readonly record struct P2PInitializationOwnerPacket(
string Name,
AccountId AccountId)
: INetSerializableStruct;
[NetworkSerialize(ArrayMaxSize = ushort.MaxValue)]
internal struct ServerPeerContentPackageOrderPacket : INetSerializableStruct
{
public string ServerName;
public ImmutableArray<ServerContentPackage> ContentPackages;
public bool AllowModDownloads;
}
[NetworkSerialize(ArrayMaxSize = ushort.MaxValue)]
internal struct PeerPacketMessage : INetSerializableStruct
{
public byte[] Buffer;
public readonly int Length => Buffer.Length;
public readonly IReadMessage GetReadMessageUncompressed() => new ReadWriteMessage(Buffer, 0, Length * 8, copyBuf: false);
public readonly IReadMessage GetReadMessage(bool isCompressed, NetworkConnection conn) => new ReadOnlyMessage(Buffer, isCompressed, 0, Length, conn);
}
[NetworkSerialize(ArrayMaxSize = byte.MaxValue)]
internal struct ClientPeerPasswordPacket : INetSerializableStruct
{
public byte[] Password;
}
[NetworkSerialize]
internal struct ServerPeerPasswordPacket : INetSerializableStruct
{
public Option<int> Salt;
public Option<int> RetriesLeft;
}
[NetworkSerialize]
internal readonly record struct DoSProtectionPacket(string EndpointStr, bool ShouldBan) : INetSerializableStruct
{
public Option<P2PEndpoint> Endpoint => P2PEndpoint.Parse(EndpointStr);
}
[NetworkSerialize]
internal readonly struct PeerDisconnectPacket : INetSerializableStruct
{
public readonly DisconnectReason DisconnectReason;
public readonly string AdditionalInformation;
private PeerDisconnectPacket(
DisconnectReason disconnectReason,
string additionalInformation = "")
{
DisconnectReason = disconnectReason;
AdditionalInformation = additionalInformation;
}
public LocalizedString ChatMessage(string? name)
{
if (string.IsNullOrEmpty(name))
{
name = TextManager.Get("ServerMessage.UnknownClient").Value;
}
LocalizedString message = DisconnectReason switch
{
DisconnectReason.Disconnected => TextManager.GetWithVariable("ServerMessage.ClientLeftServer",
"[client]", name),
DisconnectReason.Banned => TextManager.GetWithVariable("servermessage.bannedfromserver", "[client]", name),
DisconnectReason.Kicked => TextManager.GetWithVariable("servermessage.kickedfromserver", "[client]", name),
_ => TextManager.GetWithVariables("ChatMsg.DisconnectedWithReason",
("[client]", name),
("[reason]", TextManager.Get($"ChatMsg.DisconnectReason.{DisconnectReason}")))
};
if (!string.IsNullOrEmpty(AdditionalInformation) &&
DisconnectReason is DisconnectReason.Banned or DisconnectReason.Kicked)
{
message += " "+ TextManager.Get("banreason") + " " + TextManager.GetServerMessage(AdditionalInformation);
}
return message;
}
private LocalizedString MsgWithReason
=> TextManager.Get($"DisconnectReason.{DisconnectReason}")
+ "\n\n"
+ TextManager.Get("banreason") + " " + TextManager.GetServerMessage(AdditionalInformation);
private LocalizedString ServerMessage
=> TextManager.Get($"ServerMessage.{DisconnectReason}");
public LocalizedString PopupMessage
=> DisconnectReason switch
{
DisconnectReason.Banned => MsgWithReason,
DisconnectReason.Kicked => MsgWithReason,
DisconnectReason.InvalidVersion => TextManager.GetWithVariables("DisconnectMessage.InvalidVersion",
("[version]", AdditionalInformation),
("[clientversion]", GameMain.Version.ToString())),
DisconnectReason.ExcessiveDesyncOldEvent => ServerMessage,
DisconnectReason.ExcessiveDesyncRemovedEvent => ServerMessage,
DisconnectReason.SyncTimeout => ServerMessage,
DisconnectReason.AuthenticationFailed => TextManager.Get($"DisconnectReason.{DisconnectReason}").Fallback(TextManager.Get("ChatMsg.DisconnectReason.AuthenticationRequired")),
_ => TextManager.Get($"DisconnectReason.{DisconnectReason}").Fallback($"{TextManager.Get("ConnectionLost")} ({DisconnectReason})")
};
public LocalizedString ReconnectMessage
=> PopupMessage + "\n\n" + TextManager.Get("ConnectionLostReconnecting");
public PlayerConnectionChangeType ConnectionChangeType
=> DisconnectReason switch
{
DisconnectReason.Banned => PlayerConnectionChangeType.Banned,
DisconnectReason.Kicked => PlayerConnectionChangeType.Kicked,
_ => PlayerConnectionChangeType.Disconnected
};
public bool ShouldAttemptReconnect
=> DisconnectReason
is DisconnectReason.ExcessiveDesyncOldEvent
or DisconnectReason.ExcessiveDesyncRemovedEvent
or DisconnectReason.Timeout
or DisconnectReason.SyncTimeout
or DisconnectReason.SteamP2PTimeOut;
public bool IsEventSyncError
=> DisconnectReason
is DisconnectReason.ExcessiveDesyncOldEvent
or DisconnectReason.ExcessiveDesyncRemovedEvent
or DisconnectReason.SyncTimeout;
public bool ShouldCreateAnalyticsEvent
=> DisconnectReason is not (
DisconnectReason.Disconnected
or DisconnectReason.ServerShutdown
or DisconnectReason.ServerFull
or DisconnectReason.Banned
or DisconnectReason.Kicked
or DisconnectReason.TooManyFailedLogins
or DisconnectReason.InvalidVersion);
/// <summary>
/// This exists because Lidgren doesn't readily support
/// sending anything other than a string through a disconnect
/// packet, so this thing needs a sufficiently nasty string
/// representation that can be decoded with some certainty
/// that it won't get mangled by user input.
/// </summary>
public string ToLidgrenStringRepresentation()
{
static string strToBase64(string str)
=> Convert.ToBase64String(Encoding.UTF8.GetBytes(str));
return DisconnectReason
+ NetworkMagicStrings.LidgrenDisconnectSeparator
+ strToBase64(AdditionalInformation);
}
public static Option<PeerDisconnectPacket> FromLidgrenStringRepresentation(string str)
{
// Lidgren has some hardcoded disconnect strings that it uses
// when it detects that a connection has failed. We can handle
// timeouts, so let's look for strings related to that and return
// an appropriate PeerDisconnectPacket.
switch (str)
{
case Lidgren.Network.NetConnection.NoResponseMessage:
case "Connection timed out":
case "Reconnecting":
return Option.Some(WithReason(DisconnectReason.Timeout));
}
static string base64ToStr(string base64)
=> Encoding.UTF8.GetString(Convert.FromBase64String(base64));
string[] split = str.Split(NetworkMagicStrings.LidgrenDisconnectSeparator);
if (split.Length != 2) { return Option.None; }
if (!Enum.TryParse(split[0], out DisconnectReason disconnectReason)) { return Option.None; }
return Option.Some(new PeerDisconnectPacket(disconnectReason, base64ToStr(split[1])));
}
public static PeerDisconnectPacket Custom(string customMessage)
=> new PeerDisconnectPacket(
DisconnectReason.Unknown,
customMessage);
public static PeerDisconnectPacket WithReason(DisconnectReason disconnectReason)
=> new PeerDisconnectPacket(disconnectReason);
public static PeerDisconnectPacket Kicked(string? msg)
=> new PeerDisconnectPacket(DisconnectReason.Kicked, msg ?? "");
public static PeerDisconnectPacket Banned(string? msg)
=> new PeerDisconnectPacket(DisconnectReason.Banned, msg ?? "");
public static PeerDisconnectPacket InvalidVersion()
=> new PeerDisconnectPacket(
DisconnectReason.InvalidVersion,
GameMain.Version.ToString());
public static PeerDisconnectPacket SteamP2PError(Steamworks.P2PSessionError error)
=> new PeerDisconnectPacket(
DisconnectReason.SteamP2PError,
error.ToString());
public static PeerDisconnectPacket SteamAuthError(Steamworks.BeginAuthResult error)
=> new PeerDisconnectPacket(
DisconnectReason.AuthenticationFailed,
$"{nameof(Steamworks.BeginAuthResult)}.{error}");
public static PeerDisconnectPacket SteamAuthError(Steamworks.AuthResponse error)
=> new PeerDisconnectPacket(
DisconnectReason.AuthenticationFailed,
$"{nameof(Steamworks.AuthResponse)}.{error}");
}
// ReSharper disable MemberCanBePrivate.Global, FieldCanBeMadeReadOnly.Global, UnassignedField.Global
public sealed class ServerContentPackage : INetSerializableStruct
{
[NetworkSerialize]
public string Name = "";
[NetworkSerialize(ArrayMaxSize = ushort.MaxValue)]
public byte[] HashBytes = Array.Empty<byte>();
[NetworkSerialize]
public string UgcId = "";
[NetworkSerialize]
public uint InstallTimeDiffInSeconds;
[NetworkSerialize]
public bool IsMandatory;
[NetworkSerialize]
public bool IsVanilla;
private Md5Hash? cachedHash;
private DateTime? cachedDateTime;
public Md5Hash Hash
{
get => cachedHash ??= Md5Hash.BytesAsHash(HashBytes);
set
{
cachedHash = value;
HashBytes = value.ByteRepresentation;
}
}
public DateTime InstallTime => cachedDateTime ??= DateTime.UtcNow + TimeSpan.FromSeconds(InstallTimeDiffInSeconds);
public RegularPackage? RegularPackage =>
ContentPackageManager.RegularPackages.FirstOrDefault(p => p.Name.Equals(Name) && p.Hash.Equals(Hash)) ??
ContentPackageManager.RegularPackages.FirstOrDefault(p => p.Hash.Equals(Hash));
public CorePackage? CorePackage =>
ContentPackageManager.CorePackages.FirstOrDefault(p => p.Name.Equals(Name) && p.Hash.Equals(Hash)) ??
ContentPackageManager.CorePackages.FirstOrDefault(p => p.Hash.Equals(Hash));
public ContentPackage? ContentPackage => (ContentPackage?)RegularPackage ?? CorePackage;
public ServerContentPackage() { }
public ServerContentPackage(ContentPackage contentPackage, SerializableDateTime referenceTime)
{
Name = contentPackage.Name;
Hash = contentPackage.Hash;
UgcId = contentPackage.UgcId.TryUnwrap(out var ugcId)
? ugcId.StringRepresentation
: "";
IsMandatory = !contentPackage.Files.All(f => f is SubmarineFile);
IsVanilla = contentPackage == ContentPackageManager.VanillaCorePackage;
InstallTimeDiffInSeconds =
contentPackage.InstallTime.TryUnwrap(out var installTime)
? (uint)(installTime - referenceTime).TotalSeconds
: 0;
}
public string GetPackageStr() => $"\"{Name}\" (hash {Hash.ShortRepresentation})";
}
}