Client LuaCsForBarotrauma
NetworkPeerStructs.cs
1 #nullable enable
2 using System;
3 using System.Collections.Immutable;
4 using System.Linq;
5 using System.Text;
6 
7 namespace Barotrauma.Networking
8 {
9  [NetworkSerialize]
10  internal struct PeerPacketHeaders : INetSerializableStruct
11  {
12  public DeliveryMethod DeliveryMethod;
13  public PacketHeader PacketHeader;
14  public ConnectionInitialization? Initialization;
15 
16  public readonly void Deconstruct(
17  out DeliveryMethod deliveryMethod,
18  out PacketHeader packetHeader,
19  out ConnectionInitialization? initialization)
20  {
21  deliveryMethod = DeliveryMethod;
22  packetHeader = PacketHeader;
23  initialization = Initialization;
24  }
25  }
26 
27  [NetworkSerialize(ArrayMaxSize = ushort.MaxValue)]
28  internal struct ClientAuthTicketAndVersionPacket : INetSerializableStruct
29  {
30  public string Name;
31  public Option<int> OwnerKey;
32  public Option<AccountId> AccountId;
33  public Option<AuthenticationTicket> AuthTicket;
34  public string GameVersion;
35  public Identifier Language;
36  }
37 
38  [NetworkSerialize]
39  internal readonly record struct P2POwnerToServerHeader
40  (string? EndpointStr, AccountInfo AccountInfo) : INetSerializableStruct
41  {
42  public Option<P2PEndpoint> Endpoint => P2PEndpoint.Parse(EndpointStr ?? "");
43  }
44 
45  [NetworkSerialize]
46  internal readonly record struct P2PServerToOwnerHeader
47  (string? EndpointStr) : INetSerializableStruct
48  {
49  public Option<P2PEndpoint> Endpoint => P2PEndpoint.Parse(EndpointStr ?? "");
50  }
51 
52  [NetworkSerialize]
53  internal struct P2PInitializationRelayPacket : INetSerializableStruct
54  {
55  public ulong LobbyID;
56  public PeerPacketMessage Message;
57  }
58 
59  [NetworkSerialize]
60  internal readonly record struct P2PInitializationOwnerPacket(
61  string Name,
62  AccountId AccountId)
63  : INetSerializableStruct;
64 
65 
66  [NetworkSerialize(ArrayMaxSize = ushort.MaxValue)]
67  internal struct ServerPeerContentPackageOrderPacket : INetSerializableStruct
68  {
69  public string ServerName;
70  public ImmutableArray<ServerContentPackage> ContentPackages;
71  public bool AllowModDownloads;
72  }
73 
74  [NetworkSerialize(ArrayMaxSize = ushort.MaxValue)]
75  internal struct PeerPacketMessage : INetSerializableStruct
76  {
77  public byte[] Buffer;
78  public readonly int Length => Buffer.Length;
79 
80  public readonly IReadMessage GetReadMessageUncompressed() => new ReadWriteMessage(Buffer, 0, Length * 8, copyBuf: false);
81  public readonly IReadMessage GetReadMessage(bool isCompressed, NetworkConnection conn) => new ReadOnlyMessage(Buffer, isCompressed, 0, Length, conn);
82  }
83 
84  [NetworkSerialize(ArrayMaxSize = byte.MaxValue)]
85  internal struct ClientPeerPasswordPacket : INetSerializableStruct
86  {
87  public byte[] Password;
88  }
89 
90  [NetworkSerialize]
91  internal struct ServerPeerPasswordPacket : INetSerializableStruct
92  {
93  public Option<int> Salt;
94  public Option<int> RetriesLeft;
95  }
96 
97  [NetworkSerialize]
98  internal readonly struct PeerDisconnectPacket : INetSerializableStruct
99  {
100  public readonly DisconnectReason DisconnectReason;
101 
102  public readonly string AdditionalInformation;
103 
104  private PeerDisconnectPacket(
105  DisconnectReason disconnectReason,
106  string additionalInformation = "")
107  {
108  DisconnectReason = disconnectReason;
109  AdditionalInformation = additionalInformation;
110  }
111 
112  public LocalizedString ChatMessage(Client c)
113  {
114  LocalizedString message = DisconnectReason switch
115  {
116  DisconnectReason.Disconnected => TextManager.GetWithVariable("ServerMessage.ClientLeftServer",
117  "[client]", c.Name),
118  DisconnectReason.Banned => TextManager.GetWithVariable("servermessage.bannedfromserver", "[client]", c.Name),
119  DisconnectReason.Kicked => TextManager.GetWithVariable("servermessage.kickedfromserver", "[client]", c.Name),
120  _ => TextManager.GetWithVariables("ChatMsg.DisconnectedWithReason",
121  ("[client]", c.Name),
122  ("[reason]", TextManager.Get($"ChatMsg.DisconnectReason.{DisconnectReason}")))
123  };
124  if (!string.IsNullOrEmpty(AdditionalInformation) &&
125  DisconnectReason is DisconnectReason.Banned or DisconnectReason.Kicked)
126  {
127  message += " "+ TextManager.Get("banreason") + " " + TextManager.GetServerMessage(AdditionalInformation);
128  }
129  return message;
130  }
131 
132 
133  private LocalizedString MsgWithReason
134  => TextManager.Get($"DisconnectReason.{DisconnectReason}")
135  + "\n\n"
136  + TextManager.Get("banreason") + " " + TextManager.GetServerMessage(AdditionalInformation);
137 
138  private LocalizedString ServerMessage
139  => TextManager.Get($"ServerMessage.{DisconnectReason}");
140 
141  public LocalizedString PopupMessage
142  => DisconnectReason switch
143  {
144  DisconnectReason.Banned => MsgWithReason,
145  DisconnectReason.Kicked => MsgWithReason,
146  DisconnectReason.InvalidVersion => TextManager.GetWithVariables("DisconnectMessage.InvalidVersion",
147  ("[version]", AdditionalInformation),
148  ("[clientversion]", GameMain.Version.ToString())),
149  DisconnectReason.ExcessiveDesyncOldEvent => ServerMessage,
150  DisconnectReason.ExcessiveDesyncRemovedEvent => ServerMessage,
151  DisconnectReason.SyncTimeout => ServerMessage,
152  DisconnectReason.AuthenticationFailed => TextManager.Get($"DisconnectReason.{DisconnectReason}").Fallback(TextManager.Get("ChatMsg.DisconnectReason.AuthenticationRequired")),
153  _ => TextManager.Get($"DisconnectReason.{DisconnectReason}").Fallback($"{TextManager.Get("ConnectionLost")} ({DisconnectReason})")
154  };
155 
156  public LocalizedString ReconnectMessage
157  => PopupMessage + "\n\n" + TextManager.Get("ConnectionLostReconnecting");
158 
159  public PlayerConnectionChangeType ConnectionChangeType
160  => DisconnectReason switch
161  {
162  DisconnectReason.Banned => PlayerConnectionChangeType.Banned,
163  DisconnectReason.Kicked => PlayerConnectionChangeType.Kicked,
164  _ => PlayerConnectionChangeType.Disconnected
165  };
166 
167  public bool ShouldAttemptReconnect
168  => DisconnectReason
169  is DisconnectReason.ExcessiveDesyncOldEvent
170  or DisconnectReason.ExcessiveDesyncRemovedEvent
171  or DisconnectReason.Timeout
172  or DisconnectReason.SyncTimeout
173  or DisconnectReason.SteamP2PTimeOut;
174 
175  public bool IsEventSyncError
176  => DisconnectReason
177  is DisconnectReason.ExcessiveDesyncOldEvent
178  or DisconnectReason.ExcessiveDesyncRemovedEvent
179  or DisconnectReason.SyncTimeout;
180 
181  public bool ShouldCreateAnalyticsEvent
182  => DisconnectReason is not (
183  DisconnectReason.Disconnected
184  or DisconnectReason.ServerShutdown
185  or DisconnectReason.ServerFull
186  or DisconnectReason.Banned
187  or DisconnectReason.Kicked
188  or DisconnectReason.TooManyFailedLogins
189  or DisconnectReason.InvalidVersion);
190 
198  public string ToLidgrenStringRepresentation()
199  {
200  static string strToBase64(string str)
201  => Convert.ToBase64String(Encoding.UTF8.GetBytes(str));
202 
203  return DisconnectReason
204  + NetworkMagicStrings.LidgrenDisconnectSeparator
205  + strToBase64(AdditionalInformation);
206  }
207 
208  public static Option<PeerDisconnectPacket> FromLidgrenStringRepresentation(string str)
209  {
210  // Lidgren has some hardcoded disconnect strings that it uses
211  // when it detects that a connection has failed. We can handle
212  // timeouts, so let's look for strings related to that and return
213  // an appropriate PeerDisconnectPacket.
214  switch (str)
215  {
216  case Lidgren.Network.NetConnection.NoResponseMessage:
217  case "Connection timed out":
218  case "Reconnecting":
219  return Option.Some(WithReason(DisconnectReason.Timeout));
220  }
221 
222  static string base64ToStr(string base64)
223  => Encoding.UTF8.GetString(Convert.FromBase64String(base64));
224 
225  string[] split = str.Split(NetworkMagicStrings.LidgrenDisconnectSeparator);
226  if (split.Length != 2) { return Option.None; }
227  if (!Enum.TryParse(split[0], out DisconnectReason disconnectReason)) { return Option.None; }
228  return Option.Some(new PeerDisconnectPacket(disconnectReason, base64ToStr(split[1])));
229  }
230 
231  public static PeerDisconnectPacket Custom(string customMessage)
232  => new PeerDisconnectPacket(
233  DisconnectReason.Unknown,
234  customMessage);
235 
236  public static PeerDisconnectPacket WithReason(DisconnectReason disconnectReason)
237  => new PeerDisconnectPacket(disconnectReason);
238 
239  public static PeerDisconnectPacket Kicked(string? msg)
240  => new PeerDisconnectPacket(DisconnectReason.Kicked, msg ?? "");
241 
242  public static PeerDisconnectPacket Banned(string? msg)
243  => new PeerDisconnectPacket(DisconnectReason.Banned, msg ?? "");
244 
245  public static PeerDisconnectPacket InvalidVersion()
246  => new PeerDisconnectPacket(
247  DisconnectReason.InvalidVersion,
248  GameMain.Version.ToString());
249 
250  public static PeerDisconnectPacket SteamP2PError(Steamworks.P2PSessionError error)
251  => new PeerDisconnectPacket(
252  DisconnectReason.SteamP2PError,
253  error.ToString());
254 
255  public static PeerDisconnectPacket SteamAuthError(Steamworks.BeginAuthResult error)
256  => new PeerDisconnectPacket(
257  DisconnectReason.AuthenticationFailed,
258  $"{nameof(Steamworks.BeginAuthResult)}.{error}");
259 
260  public static PeerDisconnectPacket SteamAuthError(Steamworks.AuthResponse error)
261  => new PeerDisconnectPacket(
262  DisconnectReason.AuthenticationFailed,
263  $"{nameof(Steamworks.AuthResponse)}.{error}");
264  }
265 
266  // ReSharper disable MemberCanBePrivate.Global, FieldCanBeMadeReadOnly.Global, UnassignedField.Global
267  public sealed class ServerContentPackage : INetSerializableStruct
268  {
270  public string Name = "";
271 
272  [NetworkSerialize(ArrayMaxSize = ushort.MaxValue)]
273  public byte[] HashBytes = Array.Empty<byte>();
274 
276  public string UgcId = "";
277 
280 
282  public bool IsMandatory;
283 
285  public bool IsVanilla;
286 
287  private Md5Hash? cachedHash;
288  private DateTime? cachedDateTime;
289 
290  public Md5Hash Hash
291  {
292  get => cachedHash ??= Md5Hash.BytesAsHash(HashBytes);
293  set
294  {
295  cachedHash = value;
296  HashBytes = value.ByteRepresentation;
297  }
298  }
299 
300  public DateTime InstallTime => cachedDateTime ??= DateTime.UtcNow + TimeSpan.FromSeconds(InstallTimeDiffInSeconds);
302  ContentPackageManager.RegularPackages.FirstOrDefault(p => p.Name.Equals(Name) && p.Hash.Equals(Hash)) ??
303  ContentPackageManager.RegularPackages.FirstOrDefault(p => p.Hash.Equals(Hash));
304 
306  ContentPackageManager.CorePackages.FirstOrDefault(p => p.Name.Equals(Name) && p.Hash.Equals(Hash)) ??
307  ContentPackageManager.CorePackages.FirstOrDefault(p => p.Hash.Equals(Hash));
309 
310  public ServerContentPackage() { }
311 
312  public ServerContentPackage(ContentPackage contentPackage, SerializableDateTime referenceTime)
313  {
314  Name = contentPackage.Name;
315  Hash = contentPackage.Hash;
316  UgcId = contentPackage.UgcId.TryUnwrap(out var ugcId)
317  ? ugcId.StringRepresentation
318  : "";
319  IsMandatory = !contentPackage.Files.All(f => f is SubmarineFile);
320  IsVanilla = contentPackage == ContentPackageManager.VanillaCorePackage;
322  contentPackage.InstallTime.TryUnwrap(out var installTime)
323  ? (uint)(installTime - referenceTime).TotalSeconds
324  : 0;
325  }
326 
327  public string GetPackageStr() => $"\"{Name}\" (hash {Hash.ShortRepresentation})";
328  }
329 }
ImmutableArray< ContentFile > Files
readonly Option< ContentPackageId > UgcId
readonly Option< SerializableDateTime > InstallTime
static Md5Hash BytesAsHash(byte[] bytes)
Definition: Md5Hash.cs:101
Marks fields and properties as to be serialized and deserialized by INetSerializableStruct....
ServerContentPackage(ContentPackage contentPackage, SerializableDateTime referenceTime)
static new Option< P2PEndpoint > Parse(string str)
DateTime wrapper that tries to offer a reliable string representation that's also human-friendly