340 lines
14 KiB
C#
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})";
|
|
}
|
|
} |