#nullable enable using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Text; using System.Threading.Tasks; using Barotrauma.Extensions; using Lidgren.Network; using Barotrauma.Steam; using System.Net.Sockets; namespace Barotrauma.Networking { internal sealed class LidgrenClientPeer : ClientPeer { private NetClient? netClient; private readonly NetPeerConfiguration netPeerConfiguration; private readonly List incomingLidgrenMessages; public LidgrenClientPeer(LidgrenEndpoint endpoint, Callbacks callbacks, Option ownerKey) : base(endpoint, ((Endpoint)endpoint).ToEnumerable().ToImmutableArray(), callbacks, ownerKey) { ServerConnection = null; netClient = null; isActive = false; netPeerConfiguration = new NetPeerConfiguration("barotrauma") { DualStack = GameSettings.CurrentConfig.UseDualModeSockets }; if (endpoint.NetEndpoint.Address.AddressFamily == AddressFamily.InterNetworkV6) { netPeerConfiguration.LocalAddress = System.Net.IPAddress.IPv6Any; } netPeerConfiguration.DisableMessageType( NetIncomingMessageType.DebugMessage | NetIncomingMessageType.WarningMessage | NetIncomingMessageType.Receipt | NetIncomingMessageType.ErrorMessage | NetIncomingMessageType.Error); incomingLidgrenMessages = new List(); } public override void Start() { if (isActive) { return; } incomingLidgrenMessages.Clear(); ContentPackageOrderReceived = false; netClient = new NetClient(netPeerConfiguration); initializationStep = ConnectionInitialization.AuthInfoAndVersion; if (ServerEndpoint is not { } lidgrenEndpointValue) { throw new InvalidCastException($"Endpoint is not {nameof(LidgrenEndpoint)}"); } if (ServerConnection != null) { throw new InvalidOperationException("ServerConnection is not null"); } netClient.Start(); TaskPool.Add( $"{nameof(LidgrenClientPeer)}.GetAuthTicket", AuthenticationTicket.Create(ServerEndpoint), t => { if (!t.TryGetResult(out Option authenticationTicket)) { Close(PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationFailed)); return; } authTicket = authenticationTicket; var netConnection = netClient.Connect(lidgrenEndpointValue.NetEndpoint); ServerConnection = new LidgrenConnection(netConnection) { Status = NetworkConnectionStatus.Connected }; }); isActive = true; } public override void Update(float deltaTime) { if (!isActive) { return; } ToolBox.ThrowIfNull(netClient); ToolBox.ThrowIfNull(incomingLidgrenMessages); if (IsOwner && !ChildServerRelay.IsProcessAlive) { var gameClient = GameMain.Client; Close(PeerDisconnectPacket.WithReason(DisconnectReason.ServerCrashed)); gameClient?.CreateServerCrashMessage(); return; } incomingLidgrenMessages.Clear(); netClient.ReadMessages(incomingLidgrenMessages); GameMain.Client?.NetStats?.AddValue(NetStats.NetStatType.ReceivedBytes, netClient.Statistics.ReceivedBytes); GameMain.Client?.NetStats?.AddValue(NetStats.NetStatType.SentBytes, netClient.Statistics.SentBytes); foreach (NetIncomingMessage inc in incomingLidgrenMessages) { var remoteEndpoint = new LidgrenEndpoint(inc.SenderEndPoint); if (remoteEndpoint != ServerEndpoint) { DebugConsole.AddWarning($"Mismatched endpoint: expected {ServerEndpoint.NetEndpoint}, got {inc.SenderConnection.RemoteEndPoint}"); continue; } switch (inc.MessageType) { case NetIncomingMessageType.Data: HandleDataMessage(inc); break; case NetIncomingMessageType.StatusChanged: HandleStatusChanged(inc); break; } } } private void HandleDataMessage(NetIncomingMessage lidgrenMsg) { if (!isActive) { return; } ToolBox.ThrowIfNull(ServerConnection); IReadMessage inc = lidgrenMsg.ToReadMessage(); var (_, packetHeader, initialization) = INetSerializableStruct.Read(inc); if (packetHeader.IsConnectionInitializationStep()) { if (initializationStep == ConnectionInitialization.Success) { return; } ReadConnectionInitializationStep(new IncomingInitializationMessage { InitializationStep = initialization ?? throw new Exception("Initialization step missing"), Message = inc }); } else { OnInitializationComplete(); var packet = INetSerializableStruct.Read(inc); callbacks.OnMessageReceived.Invoke(packet.GetReadMessage(packetHeader.IsCompressed(), ServerConnection)); } } private void HandleStatusChanged(NetIncomingMessage inc) { if (!isActive) { return; } NetConnectionStatus status = inc.ReadHeader(); switch (status) { case NetConnectionStatus.Disconnected: string disconnectMsg = inc.ReadString(); var peerDisconnectPacket = PeerDisconnectPacket.FromLidgrenStringRepresentation(disconnectMsg); Close(peerDisconnectPacket.Fallback(PeerDisconnectPacket.WithReason(DisconnectReason.Unknown))); break; } } public override void SendPassword(string password) { if (!isActive) { return; } ToolBox.ThrowIfNull(netClient); if (initializationStep != ConnectionInitialization.Password) { return; } 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(PeerDisconnectPacket peerDisconnectPacket) { if (!isActive) { return; } ToolBox.ThrowIfNull(netClient); isActive = false; netClient.Shutdown(peerDisconnectPacket.ToLidgrenStringRepresentation()); netClient = null; callbacks.OnDisconnect.Invoke(peerDisconnectPacket); } public override void Send(IWriteMessage msg, DeliveryMethod deliveryMethod, bool compressPastThreshold = true) { if (!isActive) { return; } ToolBox.ThrowIfNull(netClient); ToolBox.ThrowIfNull(netPeerConfiguration); #if DEBUG netPeerConfiguration.SimulatedDuplicatesChance = GameMain.Client.SimulatedDuplicatesChance; netPeerConfiguration.SimulatedMinimumLatency = GameMain.Client.SimulatedMinimumLatency; netPeerConfiguration.SimulatedRandomLatency = GameMain.Client.SimulatedRandomLatency; netPeerConfiguration.SimulatedLoss = GameMain.Client.SimulatedLoss; #endif byte[] bufAux = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _); 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}\n{Environment.StackTrace}"); } } private NetSendResult ForwardToLidgren(IWriteMessage msg, DeliveryMethod deliveryMethod) { ToolBox.ThrowIfNull(netClient); return netClient.SendMessage(msg.ToLidgren(netClient), deliveryMethod.ToLidgren()); } protected override async Task> GetAccountId() { if (!EosInterface.Core.IsInitialized) { return SteamManager.GetSteamId().Select(id => (AccountId)id); } var selfPuids = EosInterface.IdQueries.GetLoggedInPuids(); if (selfPuids.None()) { return Option.None; } var accountIdsResult = await EosInterface.IdQueries.GetSelfExternalAccountIds(selfPuids.First()); return accountIdsResult.TryUnwrapSuccess(out var accountIds) && accountIds.Length > 0 ? Option.Some(accountIds[0]) : Option.None; } #if DEBUG public override void ForceTimeOut() { netClient?.ServerConnection?.ForceTimeOut(); } #endif } }