(a00338777) v0.9.2.1

This commit is contained in:
Joonas Rikkonen
2019-08-26 19:58:19 +03:00
parent 0f63da27b2
commit 80698b58b0
311 changed files with 11763 additions and 4507 deletions
@@ -1,5 +1,4 @@
using Lidgren.Network;
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -118,10 +117,23 @@ namespace Barotrauma.Networking
public bool IsBanned(IPAddress IP, ulong steamID)
{
bannedPlayers.RemoveAll(bp => bp.ExpirationTime.HasValue && DateTime.Now > bp.ExpirationTime.Value);
if (IPAddress.IsLoopback(IP)) { return false; }
return bannedPlayers.Any(bp => bp.CompareTo(IP) || (steamID > 0 && bp.SteamID == steamID));
}
public bool IsBanned(IPAddress IP)
{
if (IPAddress.IsLoopback(IP)) { return false; }
bannedPlayers.RemoveAll(bp => bp.ExpirationTime.HasValue && DateTime.Now > bp.ExpirationTime.Value);
return bannedPlayers.Any(bp => bp.CompareTo(IP));
}
public bool IsBanned(ulong steamID)
{
bannedPlayers.RemoveAll(bp => bp.ExpirationTime.HasValue && DateTime.Now > bp.ExpirationTime.Value);
return bannedPlayers.Any(bp => (steamID > 0 && bp.SteamID == steamID));
}
public void BanPlayer(string name, IPAddress ip, string reason, TimeSpan? duration)
{
string ipStr = ip.IsIPv4MappedToIPv6 ? ip.MapToIPv4().ToString() : ip.ToString();
@@ -262,7 +274,7 @@ namespace Barotrauma.Networking
}
}
public void ServerAdminWrite(NetBuffer outMsg, Client c)
public void ServerAdminWrite(IWriteMessage outMsg, Client c)
{
if (!c.HasPermission(ClientPermissions.Ban))
{
@@ -273,7 +285,7 @@ namespace Barotrauma.Networking
outMsg.Write(c.Connection == GameMain.Server.OwnerConnection);
outMsg.WritePadBits();
outMsg.WriteVariableInt32(bannedPlayers.Count);
outMsg.WriteVariableUInt32((UInt32)bannedPlayers.Count);
for (int i = 0; i < bannedPlayers.Count; i++)
{
BannedPlayer bannedPlayer = bannedPlayers[i];
@@ -289,14 +301,14 @@ namespace Barotrauma.Networking
}
}
public bool ServerAdminRead(NetBuffer incMsg, Client c)
public bool ServerAdminRead(IReadMessage incMsg, Client c)
{
if (!c.HasPermission(ClientPermissions.Ban))
{
UInt16 removeCount = incMsg.ReadUInt16();
incMsg.Position += removeCount * 4 * 8;
incMsg.BitPosition += removeCount * 4 * 8;
UInt16 rangeBanCount = incMsg.ReadUInt16();
incMsg.Position += rangeBanCount * 4 * 8;
incMsg.BitPosition += rangeBanCount * 4 * 8;
return false;
}
else
@@ -1,5 +1,4 @@
using Barotrauma.Items.Components;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
@@ -9,7 +8,7 @@ namespace Barotrauma.Networking
{
partial class ChatMessage
{
public static void ServerRead(NetIncomingMessage msg, Client c)
public static void ServerRead(IReadMessage msg, Client c)
{
c.KickAFKTimer = 0.0f;
@@ -61,22 +60,24 @@ namespace Barotrauma.Networking
}
float similarity = 0.0f;
//don't do message similarity checks on order messages
if (orderMsg == null)
for (int i = 0; i < c.LastSentChatMessages.Count; i++)
{
for (int i = 0; i < c.LastSentChatMessages.Count; i++)
float closeFactor = 1.0f / (c.LastSentChatMessages.Count - i);
if (string.IsNullOrEmpty(txt))
{
float closeFactor = 1.0f / (c.LastSentChatMessages.Count - i);
if (string.IsNullOrEmpty(txt))
{
similarity += closeFactor;
}
else
{
int levenshteinDist = ToolBox.LevenshteinDistance(txt, c.LastSentChatMessages[i]);
similarity += Math.Max((txt.Length - levenshteinDist) / (float)txt.Length * closeFactor, 0.0f);
}
similarity += closeFactor;
}
else
{
int levenshteinDist = ToolBox.LevenshteinDistance(txt, c.LastSentChatMessages[i]);
similarity += Math.Max((txt.Length - levenshteinDist) / (float)txt.Length * closeFactor, 0.0f);
}
}
//order/report messages can be sent a little faster than normal messages without triggering the spam filter
if (orderMsg != null)
{
similarity *= 0.25f;
}
bool isOwner = GameMain.Server.OwnerConnection != null && c.Connection == GameMain.Server.OwnerConnection;
@@ -153,7 +154,7 @@ namespace Barotrauma.Networking
return length;
}
public virtual void ServerWrite(NetOutgoingMessage msg, Client c)
public virtual void ServerWrite(IWriteMessage msg, Client c)
{
msg.Write((byte)ServerNetObject.CHAT_MESSAGE);
msg.Write(NetStateID);
@@ -168,4 +169,4 @@ namespace Barotrauma.Networking
}
}
}
}
}
@@ -1,5 +1,4 @@
using Lidgren.Network;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -7,8 +6,6 @@ namespace Barotrauma.Networking
{
partial class Client : IDisposable
{
public ulong SteamID;
public bool VoiceEnabled = true;
public UInt16 LastRecvClientListUpdate = 0;
@@ -61,9 +58,11 @@ namespace Barotrauma.Networking
public float DeleteDisconnectedTimer;
public CharacterInfo CharacterInfo;
public NetConnection Connection { get; set; }
public NetworkConnection Connection { get; set; }
public bool SpectateOnly;
public int KarmaKickCount;
private float karma = 100.0f;
public float Karma
@@ -108,28 +107,32 @@ namespace Barotrauma.Networking
NeedsMidRoundSync = false;
}
public static bool IsValidName(string name, GameServer server)
public static bool IsValidName(string name, ServerSettings serverSettings)
{
char[] disallowedChars = new char[] { ';', ',', '<', '>', '/', '\\', '[', ']', '"', '?' };
if (name.Any(c => disallowedChars.Contains(c))) return false;
foreach (char character in name)
{
if (!server.ServerSettings.AllowedClientNameChars.Any(charRange => (int)character >= charRange.First && (int)character <= charRange.Second)) return false;
if (!serverSettings.AllowedClientNameChars.Any(charRange => (int)character >= charRange.First && (int)character <= charRange.Second)) return false;
}
return true;
}
public bool IPMatches(string ip)
public bool EndpointMatches(string endpoint)
{
if (Connection?.RemoteEndPoint == null) { return false; }
if (Connection.RemoteEndPoint.Address.IsIPv4MappedToIPv6 &&
Connection.RemoteEndPoint.Address.MapToIPv4().ToString() == ip)
if (Connection is LidgrenConnection lidgrenConn)
{
return true;
if (lidgrenConn.IPEndPoint?.Address == null) { return false; }
if ((lidgrenConn.IPEndPoint?.Address.IsIPv4MappedToIPv6 ?? false) &&
lidgrenConn.IPEndPoint?.Address.MapToIPv4().ToString() == endpoint)
{
return true;
}
}
return Connection.RemoteEndPoint.Address.ToString() == ip;
return Connection.EndPointString == endpoint;
}
public void SetPermissions(ClientPermissions permissions, List<DebugConsole.Command> permittedConsoleCommands)
@@ -13,7 +13,7 @@ namespace Barotrauma
}
}
public void ServerWrite(Lidgren.Network.NetBuffer message, Client client, object[] extraData = null)
public void ServerWrite(IWriteMessage message, Client client, object[] extraData = null)
{
if (GameMain.Server == null) return;
@@ -1,5 +1,4 @@
using Lidgren.Network;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.IO;
@@ -12,11 +11,11 @@ namespace Barotrauma.Networking
{
public class FileTransferOut
{
private byte[] data;
private readonly byte[] data;
private DateTime startingTime;
private readonly DateTime startingTime;
private NetConnection connection;
private readonly NetworkConnection connection;
public FileTransferStatus Status;
@@ -40,7 +39,7 @@ namespace Barotrauma.Networking
public float Progress
{
get { return SentOffset / (float)Data.Length; }
get { return KnownReceivedOffset / (float)Data.Length; }
}
public float WaitTimer
@@ -54,20 +53,24 @@ namespace Barotrauma.Networking
get { return data; }
}
public bool Acknowledged;
public int SentOffset
{
get;
set;
}
public NetConnection Connection
public int KnownReceivedOffset;
public NetworkConnection Connection
{
get { return connection; }
}
public int SequenceChannel;
public int ID;
public FileTransferOut(NetConnection recipient, FileTransferType fileType, string filePath)
public FileTransferOut(NetworkConnection recipient, FileTransferType fileType, string filePath)
{
connection = recipient;
@@ -75,6 +78,10 @@ namespace Barotrauma.Networking
FilePath = filePath;
FileName = Path.GetFileName(filePath);
Acknowledged = false;
SentOffset = 0;
KnownReceivedOffset = 0;
Status = FileTransferStatus.NotStarted;
startingTime = DateTime.Now;
@@ -105,26 +112,26 @@ namespace Barotrauma.Networking
public FileTransferDelegate OnStarted;
public FileTransferDelegate OnEnded;
private List<FileTransferOut> activeTransfers;
private readonly List<FileTransferOut> activeTransfers;
private int chunkLen;
private readonly int chunkLen;
private NetPeer peer;
private readonly ServerPeer peer;
public List<FileTransferOut> ActiveTransfers
{
get { return activeTransfers; }
}
public FileSender(NetworkMember networkMember)
public FileSender(ServerPeer serverPeer, int mtu)
{
peer = networkMember.NetPeer;
chunkLen = peer.Configuration.MaximumTransmissionUnit - 100;
peer = serverPeer;
chunkLen = mtu - 100;
activeTransfers = new List<FileTransferOut>();
}
public FileTransferOut StartTransfer(NetConnection recipient, FileTransferType fileType, string filePath)
public FileTransferOut StartTransfer(NetworkConnection recipient, FileTransferType fileType, string filePath)
{
if (activeTransfers.Count >= MaxTransferCount)
{
@@ -147,11 +154,11 @@ namespace Barotrauma.Networking
{
transfer = new FileTransferOut(recipient, fileType, filePath)
{
SequenceChannel = 1
ID = 1
};
while (activeTransfers.Any(t => t.Connection == recipient && t.SequenceChannel == transfer.SequenceChannel))
while (activeTransfers.Any(t => t.Connection == recipient && t.ID == transfer.ID))
{
transfer.SequenceChannel++;
transfer.ID++;
}
activeTransfers.Add(transfer);
}
@@ -168,10 +175,10 @@ namespace Barotrauma.Networking
public void Update(float deltaTime)
{
activeTransfers.RemoveAll(t => t.Connection.Status != NetConnectionStatus.Connected);
activeTransfers.RemoveAll(t => t.Connection.Status != NetworkConnectionStatus.Connected);
var endedTransfers = activeTransfers.FindAll(t =>
t.Connection.Status != NetConnectionStatus.Connected ||
t.Connection.Status != NetworkConnectionStatus.Connected ||
t.Status == FileTransferStatus.Finished ||
t.Status == FileTransferStatus.Canceled ||
t.Status == FileTransferStatus.Error);
@@ -187,78 +194,95 @@ namespace Barotrauma.Networking
transfer.WaitTimer -= deltaTime;
if (transfer.WaitTimer > 0.0f) continue;
if (!transfer.Connection.CanSendImmediately(NetDeliveryMethod.ReliableOrdered, transfer.SequenceChannel)) continue;
transfer.WaitTimer = 0.05f;// transfer.Connection.AverageRoundtripTime;
// send another part of the file
long remaining = transfer.Data.Length - transfer.SentOffset;
int sendByteCount = (remaining > chunkLen ? chunkLen : (int)remaining);
NetOutgoingMessage message;
IWriteMessage message;
//first message; send length, chunk length, file name etc
if (transfer.SentOffset == 0)
try
{
message = peer.CreateMessage();
message.Write((byte)ServerPacketHeader.FILE_TRANSFER);
//if the recipient is the owner of the server (= a client running the server from the main exe)
//we don't need to send anything, the client can just read the file directly
if (transfer.Connection == GameMain.Server.OwnerConnection)
//first message; send length, file name etc
//wait for acknowledgement before sending data
if (!transfer.Acknowledged)
{
message.Write((byte)FileTransferMessageType.TransferOnSameMachine);
message.Write((byte)transfer.FileType);
message.Write(transfer.FilePath);
GameMain.Server.CompressOutgoingMessage(message);
transfer.Connection.SendMessage(message, NetDeliveryMethod.ReliableOrdered, transfer.SequenceChannel);
transfer.Status = FileTransferStatus.Finished;
message = new WriteOnlyMessage();
message.Write((byte)ServerPacketHeader.FILE_TRANSFER);
//if the recipient is the owner of the server (= a client running the server from the main exe)
//we don't need to send anything, the client can just read the file directly
if (transfer.Connection == GameMain.Server.OwnerConnection)
{
message.Write((byte)FileTransferMessageType.TransferOnSameMachine);
message.Write((byte)transfer.ID);
message.Write((byte)transfer.FileType);
message.Write(transfer.FilePath);
peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable);
transfer.Status = FileTransferStatus.Finished;
}
else
{
message.Write((byte)FileTransferMessageType.Initiate);
message.Write((byte)transfer.ID);
message.Write((byte)transfer.FileType);
//message.Write((ushort)chunkLen);
message.Write(transfer.Data.Length);
message.Write(transfer.FileName);
peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable);
transfer.Status = FileTransferStatus.Sending;
if (GameSettings.VerboseLogging)
{
DebugConsole.Log("Sending file transfer initiation message: ");
DebugConsole.Log(" File: " + transfer.FileName);
DebugConsole.Log(" Size: " + transfer.Data.Length);
DebugConsole.Log(" ID: " + transfer.ID);
}
}
return;
}
else
message = new WriteOnlyMessage();
message.Write((byte)ServerPacketHeader.FILE_TRANSFER);
message.Write((byte)FileTransferMessageType.Data);
message.Write((byte)transfer.ID);
message.Write(transfer.SentOffset);
byte[] sendBytes = new byte[sendByteCount];
Array.Copy(transfer.Data, transfer.SentOffset, sendBytes, 0, sendByteCount);
message.Write((ushort)sendByteCount);
message.Write(sendBytes, 0, sendByteCount);
transfer.SentOffset += sendByteCount;
if (transfer.SentOffset > transfer.KnownReceivedOffset + chunkLen * 5 ||
transfer.SentOffset >= transfer.Data.Length)
{
message.Write((byte)FileTransferMessageType.Initiate);
message.Write((byte)transfer.FileType);
message.Write((ushort)chunkLen);
message.Write((ulong)transfer.Data.Length);
message.Write(transfer.FileName);
GameMain.Server.CompressOutgoingMessage(message);
transfer.Connection.SendMessage(message, NetDeliveryMethod.ReliableOrdered, transfer.SequenceChannel);
transfer.Status = FileTransferStatus.Sending;
if (GameSettings.VerboseLogging)
{
DebugConsole.Log("Sending file transfer initiation message: ");
DebugConsole.Log(" File: " + transfer.FileName);
DebugConsole.Log(" Size: " + transfer.Data.Length);
DebugConsole.Log(" Sequence channel: " + transfer.SequenceChannel);
}
transfer.SentOffset = transfer.KnownReceivedOffset;
}
peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable);
}
message = peer.CreateMessage(1 + 1 + sendByteCount);
message.Write((byte)ServerPacketHeader.FILE_TRANSFER);
message.Write((byte)FileTransferMessageType.Data);
byte[] sendBytes = new byte[sendByteCount];
Array.Copy(transfer.Data, transfer.SentOffset, sendBytes, 0, sendByteCount);
message.Write(sendBytes);
GameMain.Server.CompressOutgoingMessage(message);
transfer.Connection.SendMessage(message, NetDeliveryMethod.ReliableOrdered, transfer.SequenceChannel);
transfer.SentOffset += sendByteCount;
catch (Exception e)
{
DebugConsole.ThrowError("FileSender threw an exception when trying to send data", e);
GameAnalyticsManager.AddErrorEventOnce(
"FileSender.Update:Exception",
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"FileSender threw an exception when trying to send data:\n" + e.Message + "\n" + e.StackTrace);
transfer.Status = FileTransferStatus.Error;
break;
}
if (GameSettings.VerboseLogging)
{
DebugConsole.Log("Sending " + sendByteCount + " bytes of the file " + transfer.FileName + " (" + transfer.SentOffset + "/" + transfer.Data.Length + " sent)");
}
if (remaining - sendByteCount <= 0)
{
transfer.Status = FileTransferStatus.Finished;
}
}
}
@@ -272,18 +296,35 @@ namespace Barotrauma.Networking
GameMain.Server.SendCancelTransferMsg(transfer);
}
public void ReadFileRequest(NetIncomingMessage inc, Client client)
public void ReadFileRequest(IReadMessage inc, Client client)
{
byte messageType = inc.ReadByte();
if (messageType == (byte)FileTransferMessageType.Cancel)
{
byte sequenceChannel = inc.ReadByte();
var matchingTransfer = activeTransfers.Find(t => t.Connection == inc.SenderConnection && t.SequenceChannel == sequenceChannel);
byte transferId = inc.ReadByte();
var matchingTransfer = activeTransfers.Find(t => t.Connection == inc.Sender && t.ID == transferId);
if (matchingTransfer != null) CancelTransfer(matchingTransfer);
return;
}
else if (messageType == (byte)FileTransferMessageType.Data)
{
byte transferId = inc.ReadByte();
var matchingTransfer = activeTransfers.Find(t => t.Connection == inc.Sender && t.ID == transferId);
if (matchingTransfer != null)
{
matchingTransfer.Acknowledged = true;
int offset = inc.ReadInt32();
matchingTransfer.KnownReceivedOffset = offset > matchingTransfer.KnownReceivedOffset ? offset : matchingTransfer.KnownReceivedOffset;
if (matchingTransfer.SentOffset < matchingTransfer.KnownReceivedOffset) { matchingTransfer.SentOffset = matchingTransfer.KnownReceivedOffset; }
if (matchingTransfer.KnownReceivedOffset >= matchingTransfer.Data.Length)
{
matchingTransfer.Status = FileTransferStatus.Finished;
}
}
}
byte fileType = inc.ReadByte();
switch (fileType)
@@ -295,17 +336,17 @@ namespace Barotrauma.Networking
if (requestedSubmarine != null)
{
StartTransfer(inc.SenderConnection, FileTransferType.Submarine, requestedSubmarine.FilePath);
StartTransfer(inc.Sender, FileTransferType.Submarine, requestedSubmarine.FilePath);
}
break;
case (byte)FileTransferType.CampaignSave:
if (GameMain.GameSession != null &&
!ActiveTransfers.Any(t => t.Connection == inc.SenderConnection && t.FileType == FileTransferType.CampaignSave))
!ActiveTransfers.Any(t => t.Connection == inc.Sender && t.FileType == FileTransferType.CampaignSave))
{
StartTransfer(inc.SenderConnection, FileTransferType.CampaignSave, GameMain.GameSession.SavePath);
StartTransfer(inc.Sender, FileTransferType.CampaignSave, GameMain.GameSession.SavePath);
if (GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign)
{
client.LastCampaignSaveSendTime = new Pair<ushort, float>(campaign.LastSaveID, (float)NetTime.Now);
client.LastCampaignSaveSendTime = new Pair<ushort, float>(campaign.LastSaveID, (float)Lidgren.Network.NetTime.Now);
}
}
break;
File diff suppressed because it is too large Load Diff
@@ -1,539 +0,0 @@
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Barotrauma.Networking
{
class UnauthenticatedClient
{
public readonly NetConnection Connection;
public readonly ulong SteamID;
public Facepunch.Steamworks.ServerAuth.Status? SteamAuthStatus = null;
public readonly int Nonce;
public int FailedAttempts;
public float AuthTimer;
public UnauthenticatedClient(NetConnection connection, int nonce, ulong steamID = 0)
{
Connection = connection;
SteamID = steamID;
Nonce = nonce;
AuthTimer = 10.0f;
FailedAttempts = 0;
}
}
partial class GameServer : NetworkMember
{
private Int32 ownerKey = 0;
List<UnauthenticatedClient> unauthenticatedClients = new List<UnauthenticatedClient>();
private void ReadClientSteamAuthRequest(NetIncomingMessage inc, NetConnection senderConnection, out ulong clientSteamID)
{
clientSteamID = 0;
if (!Steam.SteamManager.USE_STEAM)
{
DebugConsole.Log("Received a Steam auth request from " + senderConnection.RemoteEndPoint + ". Steam authentication not required, handling auth normally.");
//not using steam, handle auth normally
HandleClientAuthRequest(senderConnection, 0);
return;
}
if (senderConnection == OwnerConnection)
{
//the client is the owner of the server, no need for authentication
//(it would fail with a "duplicate request" error anyway)
HandleClientAuthRequest(senderConnection, 0);
return;
}
clientSteamID = inc.ReadUInt64();
int authTicketLength = inc.ReadInt32();
inc.ReadBytes(authTicketLength, out byte[] authTicketData);
DebugConsole.Log("Received a Steam auth request");
DebugConsole.Log(" Steam ID: "+ clientSteamID);
DebugConsole.Log(" Auth ticket length: " + authTicketLength);
DebugConsole.Log(" Auth ticket data: " +
((authTicketData == null) ? "null" : ToolBox.LimitString(string.Concat(authTicketData.Select(b => b.ToString("X2"))), 16)));
if (senderConnection != OwnerConnection &&
serverSettings.BanList.IsBanned(senderConnection.RemoteEndPoint.Address, clientSteamID))
{
return;
}
ulong steamID = clientSteamID;
if (unauthenticatedClients.Any(uc => uc.Connection == inc.SenderConnection))
{
var steamAuthedClient = unauthenticatedClients.Find(uc =>
uc.Connection == inc.SenderConnection &&
uc.SteamID == steamID &&
uc.SteamAuthStatus == Facepunch.Steamworks.ServerAuth.Status.OK);
if (steamAuthedClient != null)
{
DebugConsole.Log("Client already authenticated, sending AUTH_RESPONSE again...");
HandleClientAuthRequest(inc.SenderConnection, steamID);
}
DebugConsole.Log("Steam authentication already pending...");
return;
}
if (authTicketData == null)
{
DebugConsole.Log("Invalid request");
return;
}
unauthenticatedClients.RemoveAll(uc => uc.Connection == senderConnection);
int nonce = CryptoRandom.Instance.Next();
var unauthClient = new UnauthenticatedClient(senderConnection, nonce, clientSteamID)
{
AuthTimer = 20
};
unauthenticatedClients.Add(unauthClient);
if (!Steam.SteamManager.StartAuthSession(authTicketData, clientSteamID))
{
unauthenticatedClients.Remove(unauthClient);
if (GameMain.Config.RequireSteamAuthentication)
{
unauthClient.Connection.Disconnect(DisconnectReason.SteamAuthenticationFailed.ToString());
Log("Disconnected unauthenticated client (Steam ID: " + steamID + "). Steam authentication failed.", ServerLog.MessageType.ServerMessage);
}
else
{
DebugConsole.Log("Steam authentication failed, skipping to basic auth...");
HandleClientAuthRequest(senderConnection);
return;
}
}
return;
}
public void OnAuthChange(ulong steamID, ulong ownerID, Facepunch.Steamworks.ServerAuth.Status status)
{
DebugConsole.Log("************ OnAuthChange");
DebugConsole.Log(" Steam ID: " + steamID);
DebugConsole.Log(" Owner ID: " + ownerID);
DebugConsole.Log(" Status: " + status);
UnauthenticatedClient unauthClient = unauthenticatedClients.Find(uc => uc.SteamID == ownerID);
if (unauthClient != null)
{
unauthClient.SteamAuthStatus = status;
switch (status)
{
case Facepunch.Steamworks.ServerAuth.Status.OK:
////steam authentication done, check password next
Log("Successfully authenticated client via Steam (Steam ID: " + steamID + ").", ServerLog.MessageType.ServerMessage);
HandleClientAuthRequest(unauthClient.Connection, unauthClient.SteamID);
break;
default:
unauthenticatedClients.Remove(unauthClient);
if (GameMain.Config.RequireSteamAuthentication)
{
Log("Disconnected unauthenticated client (Steam ID: " + steamID + "). Steam authentication failed, (" + status + ").", ServerLog.MessageType.ServerMessage);
unauthClient.Connection.Disconnect(DisconnectReason.SteamAuthenticationFailed.ToString() + "/ (" + status.ToString() + ")");
}
else
{
DebugConsole.Log("Steam authentication failed (" + status.ToString() + "), skipping to basic auth...");
HandleClientAuthRequest(unauthClient.Connection);
return;
}
break;
}
return;
}
else
{
DebugConsole.Log(" No unauthenticated clients found with the Steam ID " + steamID);
}
//kick connected client if status becomes invalid (e.g. VAC banned, not connected to steam)
/*if (status != Facepunch.Steamworks.ServerAuth.Status.OK && GameMain.Config.RequireSteamAuthentication)
{
var connectedClient = connectedClients.Find(c => c.SteamID == ownerID);
if (connectedClient != null)
{
Log("Disconnecting client " + connectedClient.Name + " (Steam ID: " + steamID + "). Steam authentication no longer valid (" + status + ").", ServerLog.MessageType.ServerMessage);
KickClient(connectedClient, $"DisconnectMessage.SteamAuthNoLongerValid~[status]={status.ToString()}");
}
}*/
}
private bool IsServerOwner(NetIncomingMessage inc, NetConnection senderConnection)
{
string address = senderConnection.RemoteEndPoint.Address.MapToIPv4().ToString();
int incKey = inc.ReadInt32();
if (ownerKey == 0)
{
return false; //ownership key has been destroyed or has never existed
}
if (address.ToString() != "127.0.0.1")
{
return false; //not localhost
}
if (incKey != ownerKey)
{
return false; //incorrect owner key, how did this even happen
}
return true;
}
private void HandleOwnership(NetIncomingMessage inc, NetConnection senderConnection)
{
DebugConsole.Log("HandleOwnership (" + senderConnection.RemoteEndPoint.Address + ")");
if (IsServerOwner(inc, senderConnection))
{
ownerKey = 0; //destroy owner key so nobody else can take ownership of the server
OwnerConnection = senderConnection;
DebugConsole.NewMessage("Successfully set up server owner", Color.Lime);
}
}
private void HandleClientAuthRequest(NetConnection connection, ulong steamID = 0)
{
DebugConsole.Log("HandleClientAuthRequest (steamID " + steamID + ")");
if (GameMain.Config.RequireSteamAuthentication && connection != OwnerConnection && steamID == 0)
{
DebugConsole.Log("Disconnecting " + connection.RemoteEndPoint + ", Steam authentication required.");
connection.Disconnect(DisconnectReason.SteamAuthenticationRequired.ToString());
return;
}
//client wants to know if server requires password
if (ConnectedClients.Find(c => c.Connection == connection) != null)
{
//this client has already been authenticated
return;
}
UnauthenticatedClient unauthClient = unauthenticatedClients.Find(uc => uc.Connection == connection);
if (unauthClient == null)
{
DebugConsole.Log("Unauthed client, generating a nonce...");
//new client, generate nonce and add to unauth queue
if (ConnectedClients.Count >= serverSettings.MaxPlayers)
{
//server is full, can't allow new connection
connection.Disconnect(DisconnectReason.ServerFull.ToString());
if (steamID > 0) { Steam.SteamManager.StopAuthSession(steamID); }
return;
}
int nonce = CryptoRandom.Instance.Next();
unauthClient = new UnauthenticatedClient(connection, nonce, steamID);
unauthenticatedClients.Add(unauthClient);
}
unauthClient.AuthTimer = 10.0f;
//if the client is already in the queue, getting another unauth request means that our response was lost; resend
NetOutgoingMessage nonceMsg = server.CreateMessage();
nonceMsg.Write((byte)ServerPacketHeader.AUTH_RESPONSE);
if (serverSettings.HasPassword && connection != OwnerConnection)
{
nonceMsg.Write(true); //true = password
nonceMsg.Write((Int32)unauthClient.Nonce); //here's nonce, encrypt with this
}
else
{
nonceMsg.Write(false); //false = no password
}
CompressOutgoingMessage(nonceMsg);
DebugConsole.Log("Sending auth response...");
server.SendMessage(nonceMsg, connection, NetDeliveryMethod.Unreliable);
}
private void ClientInitRequest(NetIncomingMessage inc)
{
DebugConsole.Log("Received client init request");
if (ConnectedClients.Find(c => c.Connection == inc.SenderConnection) != null)
{
//this client was already authenticated
//another init request means they didn't get any update packets yet
DebugConsole.Log("Client already connected, ignoring...");
return;
}
UnauthenticatedClient unauthClient = unauthenticatedClients.Find(uc => uc.Connection == inc.SenderConnection);
if (unauthClient == null)
{
//client did not ask for nonce first, can't authorize
inc.SenderConnection.Disconnect(DisconnectReason.AuthenticationRequired.ToString());
if (unauthClient.SteamID > 0) { Steam.SteamManager.StopAuthSession(unauthClient.SteamID); }
return;
}
if (serverSettings.HasPassword && inc.SenderConnection != OwnerConnection)
{
//decrypt message and compare password
string clPw = inc.ReadString();
if (!serverSettings.IsPasswordCorrect(clPw, unauthClient.Nonce))
{
unauthClient.FailedAttempts++;
if (unauthClient.FailedAttempts > 3)
{
//disconnect and ban after too many failed attempts
serverSettings.BanList.BanPlayer("Unnamed", unauthClient.Connection.RemoteEndPoint.Address, "DisconnectMessage.TooManyFailedLogins", duration: null);
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.TooManyFailedLogins, "");
Log(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " has been banned from the server (too many wrong passwords)", ServerLog.MessageType.Error);
DebugConsole.NewMessage(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " has been banned from the server (too many wrong passwords)", Color.Red);
return;
}
else
{
//not disconnecting the player here, because they'll still use the same connection and nonce if they try logging in again
NetOutgoingMessage reject = server.CreateMessage();
reject.Write((byte)ServerPacketHeader.AUTH_FAILURE);
reject.Write("Wrong password! You have " + Convert.ToString(4 - unauthClient.FailedAttempts) + " more attempts before you're banned from the server.");
Log(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " failed to join the server (incorrect password)", ServerLog.MessageType.Error);
DebugConsole.NewMessage(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " failed to join the server (incorrect password)", Color.Red);
CompressOutgoingMessage(reject);
server.SendMessage(reject, unauthClient.Connection, NetDeliveryMethod.Unreliable);
unauthClient.AuthTimer = 10.0f;
return;
}
}
}
string clVersion = inc.ReadString();
UInt16 contentPackageCount = inc.ReadUInt16();
List<string> contentPackageNames = new List<string>();
List<string> contentPackageHashes = new List<string>();
for (int i = 0; i < contentPackageCount; i++)
{
string packageName = inc.ReadString();
string packageHash = inc.ReadString();
contentPackageNames.Add(packageName);
contentPackageHashes.Add(packageHash);
if (contentPackageCount == 0)
{
DebugConsole.Log("Client is using content package " +
(packageName ?? "null") + " (" + (packageHash ?? "null" + ")"));
}
}
if (contentPackageCount == 0)
{
DebugConsole.Log("Client did not list any content packages.");
}
string clName = Client.SanitizeName(inc.ReadString());
if (string.IsNullOrWhiteSpace(clName))
{
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.NoName, "");
Log(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " couldn't join the server (no name given)", ServerLog.MessageType.Error);
DebugConsole.NewMessage(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " couldn't join the server (no name given)", Color.Red);
return;
}
bool? isCompatibleVersion = IsCompatible(clVersion, GameMain.Version.ToString());
if (isCompatibleVersion.HasValue && !isCompatibleVersion.Value)
{
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.InvalidVersion,
$"DisconnectMessage.InvalidVersion~[version]={GameMain.Version.ToString()}~[clientversion]={clVersion}");
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (incompatible game version)", ServerLog.MessageType.Error);
DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (incompatible game version)", Color.Red);
return;
}
//check if the client is missing any of the content packages the server requires
List<ContentPackage> missingPackages = new List<ContentPackage>();
foreach (ContentPackage contentPackage in GameMain.SelectedPackages)
{
if (!contentPackage.HasMultiplayerIncompatibleContent) continue;
bool packageFound = false;
for (int i = 0; i < contentPackageCount; i++)
{
if (contentPackageNames[i] == contentPackage.Name && contentPackageHashes[i] == contentPackage.MD5hash.Hash)
{
packageFound = true;
break;
}
}
if (!packageFound) missingPackages.Add(contentPackage);
}
if (missingPackages.Count == 1)
{
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.MissingContentPackage, $"DisconnectMessage.MissingContentPackage~[missingcontentpackage]={GetPackageStr(missingPackages[0])}");
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (missing content package " + GetPackageStr(missingPackages[0]) + ")", ServerLog.MessageType.Error);
return;
}
else if (missingPackages.Count > 1)
{
List<string> packageStrs = new List<string>();
missingPackages.ForEach(cp => packageStrs.Add(GetPackageStr(cp)));
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.MissingContentPackage, $"DisconnectMessage.MissingContentPackages~[missingcontentpackages]={string.Join(", ", packageStrs)}");
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (missing content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error);
return;
}
string GetPackageStr(ContentPackage contentPackage)
{
return "\"" + contentPackage.Name + "\" (hash " + contentPackage.MD5hash.ShortHash + ")";
}
//check if the client is using any contentpackages that are not compatible with the server
List<Pair<string, string>> incompatiblePackages = new List<Pair<string, string>>();
for (int i = 0; i < contentPackageNames.Count; i++)
{
if (!GameMain.Config.SelectedContentPackages.Any(cp => cp.Name == contentPackageNames[i] && cp.MD5hash.Hash == contentPackageHashes[i]))
{
incompatiblePackages.Add(new Pair<string, string>(contentPackageNames[i], contentPackageHashes[i]));
}
}
if (incompatiblePackages.Count == 1)
{
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.IncompatibleContentPackage,
$"DisconnectMessage.IncompatibleContentPackage~[incompatiblecontentpackage]={GetPackageStr2(incompatiblePackages[0])}");
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (incompatible content package " + GetPackageStr2(incompatiblePackages[0]) + ")", ServerLog.MessageType.Error);
return;
}
else if (incompatiblePackages.Count > 1)
{
List<string> packageStrs = new List<string>();
incompatiblePackages.ForEach(cp => packageStrs.Add(GetPackageStr2(cp)));
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.IncompatibleContentPackage,
$"DisconnectMessage.IncompatibleContentPackages~[incompatiblecontentpackages]={string.Join(", ", packageStrs)}");
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (incompatible content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error);
return;
}
string GetPackageStr2(Pair<string, string> nameAndHash)
{
return "\"" + nameAndHash.First + "\" (hash " + Md5Hash.GetShortHash(nameAndHash.Second) + ")";
}
if (inc.SenderConnection != OwnerConnection && !serverSettings.Whitelist.IsWhiteListed(clName, inc.SenderConnection.RemoteEndPoint.Address))
{
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.NotOnWhitelist, "");
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (not in whitelist)", ServerLog.MessageType.Error);
DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (not in whitelist)", Color.Red);
return;
}
if (!Client.IsValidName(clName, this))
{
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.InvalidName, "");
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (invalid name)", ServerLog.MessageType.Error);
DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (invalid name)", Color.Red);
return;
}
if (inc.SenderConnection != OwnerConnection && Homoglyphs.Compare(clName.ToLower(), Name.ToLower()))
{
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.NameTaken, "");
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (name taken by the server)", ServerLog.MessageType.Error);
DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (name taken by the server)", Color.Red);
return;
}
Client nameTaken = ConnectedClients.Find(c => Homoglyphs.Compare(c.Name.ToLower(), clName.ToLower()));
if (nameTaken != null)
{
if (nameTaken.Connection.RemoteEndPoint.Address.ToString() == inc.SenderEndPoint.Address.ToString())
{
//both name and IP address match, replace this player's connection
nameTaken.Connection.Disconnect(DisconnectReason.SessionTaken.ToString());
nameTaken.Connection = unauthClient.Connection;
nameTaken.InitClientSync(); //reinitialize sync ids because this is a new connection
unauthenticatedClients.Remove(unauthClient);
unauthClient = null;
return;
}
else
{
//can't authorize this client
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.NameTaken, "");
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (name already taken)", ServerLog.MessageType.Error);
DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (name already taken)", Color.Red);
return;
}
}
//new client
Client newClient = new Client(clName, GetNewClientID());
newClient.InitClientSync();
newClient.Connection = unauthClient.Connection;
newClient.SteamID = unauthClient.SteamID;
unauthenticatedClients.Remove(unauthClient);
unauthClient = null;
ConnectedClients.Add(newClient);
var previousPlayer = previousPlayers.Find(p => p.MatchesClient(newClient));
if (previousPlayer != null)
{
newClient.Karma = previousPlayer.Karma;
foreach (Client c in previousPlayer.KickVoters)
{
if (!connectedClients.Contains(c)) { continue; }
newClient.AddKickVote(c);
}
}
LastClientListUpdateID++;
if (newClient.Connection == OwnerConnection)
{
newClient.GivePermission(ClientPermissions.All);
newClient.PermittedConsoleCommands.AddRange(DebugConsole.Commands);
GameMain.Server.UpdateClientPermissions(newClient);
GameMain.Server.SendConsoleMessage("Granted all permissions to " + newClient.Name + ".", newClient);
}
GameMain.Server.SendChatMessage($"ServerMessage.JoinedServer~[client]={clName}", ChatMessageType.Server, null);
serverSettings.ServerDetailsChanged = true;
if (previousPlayer != null && previousPlayer.Name != newClient.Name)
{
GameMain.Server.SendChatMessage($"ServerMessage.PreviousClientName~[client]={clName}~[previousname]={previousPlayer.Name}", ChatMessageType.Server, null);
previousPlayer.Name = newClient.Name;
}
var savedPermissions = serverSettings.ClientPermissions.Find(cp =>
cp.SteamID > 0 ?
cp.SteamID == newClient.SteamID :
newClient.IPMatches(cp.IP));
if (savedPermissions != null)
{
newClient.SetPermissions(savedPermissions.Permissions, savedPermissions.PermittedCommands);
}
else
{
var defaultPerms = PermissionPreset.List.Find(p => p.Name == "None");
if (defaultPerms != null)
{
newClient.SetPermissions(defaultPerms.Permissions, defaultPerms.PermittedCommands);
}
else
{
newClient.SetPermissions(ClientPermissions.None, new List<DebugConsole.Command>());
}
}
}
private void DisconnectUnauthClient(NetIncomingMessage inc, UnauthenticatedClient unauthClient, DisconnectReason reason, string message)
{
inc.SenderConnection.Disconnect(reason.ToString() + "/ " + TextManager.GetServerMessage(message));
if (unauthClient.SteamID > 0) { Steam.SteamManager.StopAuthSession(unauthClient.SteamID); }
if (unauthClient != null)
{
unauthenticatedClients.Remove(unauthClient);
}
}
}
}
@@ -66,7 +66,15 @@ namespace Barotrauma
foreach (Client bannedClient in bannedClients)
{
GameMain.Server.BanClient(bannedClient, $"KarmaBanned~[banthreshold]={(int)KickBanThreshold}", duration: TimeSpan.FromSeconds(GameMain.Server.ServerSettings.AutoBanTime));
if (bannedClient.KarmaKickCount < KicksBeforeBan)
{
GameMain.Server.KickClient(bannedClient, $"KarmaKicked~[banthreshold]={(int)KickBanThreshold}", resetKarma: true);
}
else
{
GameMain.Server.BanClient(bannedClient, $"KarmaBanned~[banthreshold]={(int)KickBanThreshold}", duration: TimeSpan.FromSeconds(GameMain.Server.ServerSettings.AutoBanTime));
}
bannedClient.KarmaKickCount++;
}
}
@@ -79,7 +87,7 @@ namespace Barotrauma
if (TestMode)
{
string msg =
karmaChange < 0 ? $"You karma has decreased to {client.Karma}" : $"You karma has increased to {client.Karma}";
karmaChange < 0 ? $"Your karma has decreased to {client.Karma}" : $"Your karma has increased to {client.Karma}";
if (!string.IsNullOrEmpty(debugKarmaChangeReason))
{
msg += $". Reason: {debugKarmaChangeReason}";
@@ -100,17 +108,17 @@ namespace Barotrauma
private void UpdateClient(Client client, float deltaTime)
{
if (client.Karma > KarmaDecayThreshold)
if (client.Character != null && !client.Character.Removed && !client.Character.IsDead)
{
client.Karma -= KarmaDecay * deltaTime;
}
else if (client.Karma < KarmaIncreaseThreshold)
{
client.Karma += KarmaIncrease * deltaTime;
}
if (client.Karma > KarmaDecayThreshold)
{
client.Karma -= KarmaDecay * deltaTime;
}
else if (client.Karma < KarmaIncreaseThreshold)
{
client.Karma += KarmaIncrease * deltaTime;
}
if (client.Character != null && !client.Character.Removed)
{
//increase the strength of the herpes affliction in steps instead of linearly
//otherwise clients could determine their exact karma value from the strength
float herpesStrength = 0.0f;
@@ -129,6 +137,10 @@ namespace Barotrauma
else if (existingAffliction != null)
{
existingAffliction.Strength = herpesStrength;
if (herpesStrength <= 0.0f)
{
client.Character.CharacterHealth.ReduceAffliction(null, "invertcontrols", 100.0f);
}
}
//check if the client has disconnected an excessive number of wires
@@ -182,19 +194,29 @@ namespace Barotrauma
if (target.IsDead || target.Removed) { return; }
bool isEnemy = target.AIController is EnemyAIController || target.TeamID != attacker.TeamID;
if (GameMain.Server.TraitorManager != null)
if (GameMain.Server.TraitorManager?.Traitors != null)
{
if (GameMain.Server.TraitorManager.TraitorList.Any(t => t.Character == target))
if (GameMain.Server.TraitorManager.Traitors.Any(t => t.Character == target))
{
//traitors always count as enemies
isEnemy = true;
}
if (GameMain.Server.TraitorManager.TraitorList.Any(t => t.Character == attacker && t.TargetCharacter == target))
if (GameMain.Server.TraitorManager.Traitors.Any(t =>
t.Character == attacker &&
t.CurrentObjective != null &&
t.CurrentObjective.IsEnemy(target)))
{
//target counts as an enemy to the traitor
isEnemy = true;
}
}
//attacking/healing clowns has a smaller effect on karma
if (target.HasEquippedItem("clownmask") &&
target.HasEquippedItem("clowncostume"))
{
damage *= 0.5f;
}
if (appliedAfflictions != null)
{
@@ -205,7 +227,7 @@ namespace Barotrauma
}
}
if (target.AIController is EnemyAIController || target.TeamID != attacker.TeamID)
if (isEnemy)
{
if (damage > 0)
{
@@ -242,6 +264,19 @@ namespace Barotrauma
if (damageAmount > 0)
{
if (StructureDamageKarmaDecrease <= 0.0f) { return; }
if (GameMain.Server.TraitorManager?.Traitors != null)
{
if (GameMain.Server.TraitorManager.Traitors.Any(t =>
t.Character == attacker &&
t.CurrentObjective != null &&
t.CurrentObjective.HasGoalsOfType<Traitor.GoalFloodPercentOfSub>()))
{
//traitor tasked to flood the sub -> damaging structures is ok
return;
}
}
Client client = GameMain.Server.ConnectedClients.Find(c => c.Character == attacker);
if (client != null)
{
@@ -327,6 +362,13 @@ namespace Barotrauma
Client client = GameMain.Server.ConnectedClients.Find(c => c.Character == target);
if (client == null) { return; }
//all penalties/rewards are halved when wearing a clown costume
if (target.HasEquippedItem("clownmask") &&
target.HasEquippedItem("clowncostume"))
{
amount *= 0.5f;
}
client.Karma += amount;
if (TestMode)
{
@@ -1,5 +1,4 @@
using Barotrauma.Extensions;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -32,7 +31,7 @@ namespace Barotrauma.Networking
#endif
}
public void Write(NetBuffer msg, Client recipient)
public void Write(IWriteMessage msg, Client recipient)
{
serializable.ServerWrite(msg, recipient, Data);
}
@@ -66,7 +65,7 @@ namespace Barotrauma.Networking
public readonly Client Sender;
public readonly UInt16 CharacterStateID;
public readonly NetBuffer Data;
public readonly ReadWriteMessage Data;
public readonly Character Character;
@@ -74,7 +73,7 @@ namespace Barotrauma.Networking
public bool IsProcessed;
public BufferedEvent(Client sender, Character senderCharacter, UInt16 characterStateID, IClientSerializable targetEntity, NetBuffer data)
public BufferedEvent(Client sender, Character senderCharacter, UInt16 characterStateID, IClientSerializable targetEntity, ReadWriteMessage data)
{
this.Sender = sender;
this.Character = senderCharacter;
@@ -300,7 +299,7 @@ namespace Barotrauma.Networking
/// <summary>
/// Writes all the events that the client hasn't received yet into the outgoing message
/// </summary>
public void Write(Client client, NetOutgoingMessage msg)
public void Write(Client client, IWriteMessage msg)
{
Write(client, msg, out _);
}
@@ -308,7 +307,7 @@ namespace Barotrauma.Networking
/// <summary>
/// Writes all the events that the client hasn't received yet into the outgoing message
/// </summary>
public void Write(Client client, NetOutgoingMessage msg, out List<NetEntityEvent> sentEvents)
public void Write(Client client, IWriteMessage msg, out List<NetEntityEvent> sentEvents)
{
List<NetEntityEvent> eventsToSync = null;
if (client.NeedsMidRoundSync)
@@ -371,7 +370,7 @@ namespace Barotrauma.Networking
foreach (NetEntityEvent entityEvent in sentEvents)
{
(entityEvent as ServerEntityEvent).Sent = true;
client.EntityEventLastSent[entityEvent.ID] = NetTime.Now;
client.EntityEventLastSent[entityEvent.ID] = Lidgren.Network.NetTime.Now;
}
}
@@ -399,9 +398,10 @@ namespace Barotrauma.Networking
//find the first event that hasn't been sent in roundtriptime or at all
client.EntityEventLastSent.TryGetValue(eventList[i].ID, out double lastSent);
float minInterval = Math.Max(client.Connection.AverageRoundtripTime, (float)server.UpdateInterval.TotalSeconds * 2);
float avgRoundtripTime = 0.01f; //TODO: reimplement client.Connection.AverageRoundtripTime
float minInterval = Math.Max(avgRoundtripTime, (float)server.UpdateInterval.TotalSeconds * 2);
if (lastSent > NetTime.Now - Math.Min(minInterval, 0.5f))
if (lastSent > Lidgren.Network.NetTime.Now - Math.Min(minInterval, 0.5f))
{
continue;
}
@@ -444,7 +444,7 @@ namespace Barotrauma.Networking
/// <summary>
/// Read the events from the message, ignoring ones we've already received
/// </summary>
public void Read(NetIncomingMessage msg, Client sender = null)
public void Read(IReadMessage msg, Client sender = null)
{
UInt16 firstEventID = msg.ReadUInt16();
int eventCount = msg.ReadByte();
@@ -472,7 +472,7 @@ namespace Barotrauma.Networking
{
DebugConsole.NewMessage("Received msg " + thisEventID, Color.Red);
}
msg.Position += msgLength * 8;
msg.BitPosition += msgLength * 8;
}
else if (entity == null)
{
@@ -486,7 +486,7 @@ namespace Barotrauma.Networking
Microsoft.Xna.Framework.Color.Orange);
}
sender.LastSentEntityEventID++;
msg.Position += msgLength * 8;
msg.BitPosition += msgLength * 8;
}
else
{
@@ -497,8 +497,10 @@ namespace Barotrauma.Networking
UInt16 characterStateID = msg.ReadUInt16();
NetBuffer buffer = new NetBuffer();
buffer.Write(msg.ReadBytes(msgLength - 2));
ReadWriteMessage buffer = new ReadWriteMessage();
byte[] temp = msg.ReadBytes(msgLength - 2);
buffer.Write(temp, 0, msgLength - 2);
buffer.BitPosition = 0;
BufferEvent(new BufferedEvent(sender, sender.Character, characterStateID, entity, buffer));
sender.LastSentEntityEventID++;
@@ -507,7 +509,7 @@ namespace Barotrauma.Networking
}
}
protected override void WriteEvent(NetBuffer buffer, NetEntityEvent entityEvent, Client recipient = null)
protected override void WriteEvent(IWriteMessage buffer, NetEntityEvent entityEvent, Client recipient = null)
{
var serverEvent = entityEvent as ServerEntityEvent;
if (serverEvent == null) return;
@@ -515,7 +517,7 @@ namespace Barotrauma.Networking
serverEvent.Write(buffer, recipient);
}
protected void ReadEvent(NetBuffer buffer, INetSerializable entity, Client sender = null)
protected void ReadEvent(IReadMessage buffer, INetSerializable entity, Client sender = null)
{
var clientEntity = entity as IClientSerializable;
if (clientEntity == null) return;
@@ -1,13 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
using Lidgren.Network;
namespace Barotrauma.Networking
{
partial class OrderChatMessage : ChatMessage
{
public override void ServerWrite(NetOutgoingMessage msg, Client c)
public override void ServerWrite(IWriteMessage msg, Client c)
{
msg.Write((byte)ServerNetObject.CHAT_MESSAGE);
msg.Write(NetStateID);
@@ -0,0 +1,674 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Linq;
using Lidgren.Network;
using Facepunch.Steamworks;
namespace Barotrauma.Networking
{
class LidgrenServerPeer : ServerPeer
{
private ServerSettings serverSettings;
private NetPeerConfiguration netPeerConfiguration;
private NetServer netServer;
private Facepunch.Steamworks.Server steamServer;
private class PendingClient
{
public string Name;
public int OwnerKey;
public NetConnection Connection;
public ConnectionInitialization InitializationStep;
public double UpdateTime;
public double TimeOut;
public int Retries;
public UInt64? SteamID;
public Int32? PasswordSalt;
public bool AuthSessionStarted;
public PendingClient(NetConnection conn)
{
OwnerKey = 0;
Connection = conn;
InitializationStep = ConnectionInitialization.SteamTicketAndVersion;
Retries = 0;
SteamID = null;
PasswordSalt = null;
UpdateTime = Timing.TotalTime;
TimeOut = 20.0;
AuthSessionStarted = false;
}
}
private List<LidgrenConnection> connectedClients;
private List<PendingClient> pendingClients;
private List<NetIncomingMessage> incomingLidgrenMessages;
public LidgrenServerPeer(int? ownKey, ServerSettings settings)
{
serverSettings = settings;
netServer = null;
connectedClients = new List<LidgrenConnection>();
pendingClients = new List<PendingClient>();
incomingLidgrenMessages = new List<NetIncomingMessage>();
steamServer = null;
ownerKey = ownKey;
}
public override void Start()
{
if (netServer != null) { return; }
netPeerConfiguration = new NetPeerConfiguration("barotrauma");
netPeerConfiguration.AcceptIncomingConnections = true;
netPeerConfiguration.AutoExpandMTU = false;
netPeerConfiguration.MaximumConnections = serverSettings.MaxPlayers * 2;
netPeerConfiguration.EnableUPnP = serverSettings.EnableUPnP;
netPeerConfiguration.Port = serverSettings.Port;
netPeerConfiguration.DisableMessageType(NetIncomingMessageType.DebugMessage |
NetIncomingMessageType.WarningMessage | NetIncomingMessageType.Receipt |
NetIncomingMessageType.ErrorMessage | NetIncomingMessageType.Error |
NetIncomingMessageType.UnconnectedData);
netPeerConfiguration.EnableMessageType(NetIncomingMessageType.ConnectionApproval);
netServer = new NetServer(netPeerConfiguration);
netServer.Start();
if (serverSettings.EnableUPnP)
{
InitUPnP();
while (DiscoveringUPnP()) { }
FinishUPnP();
}
}
public override void Close(string msg=null)
{
if (netServer == null) { return; }
for (int i=pendingClients.Count-1;i>=0;i--)
{
RemovePendingClient(pendingClients[i], msg ?? DisconnectReason.ServerShutdown.ToString());
}
for (int i=connectedClients.Count-1;i>=0;i--)
{
Disconnect(connectedClients[i], msg ?? DisconnectReason.ServerShutdown.ToString());
}
netServer.Shutdown(msg ?? DisconnectReason.ServerShutdown.ToString());
pendingClients.Clear();
connectedClients.Clear();
netServer = null;
if (steamServer != null)
{
steamServer.Auth.OnAuthChange = null;
}
steamServer = null;
OnShutdown?.Invoke();
}
public override void Update(float deltaTime)
{
if (netServer == null) { return; }
if (OnOwnerDetermined != null && OwnerConnection != null)
{
OnOwnerDetermined?.Invoke(OwnerConnection);
OnOwnerDetermined = null;
}
netServer.ReadMessages(incomingLidgrenMessages);
//process incoming connections first
foreach (NetIncomingMessage inc in incomingLidgrenMessages.Where(m => m.MessageType == NetIncomingMessageType.ConnectionApproval))
{
HandleConnection(inc);
}
try
{
//after processing connections, go ahead with the rest of the messages
foreach (NetIncomingMessage inc in incomingLidgrenMessages.Where(m => m.MessageType != NetIncomingMessageType.ConnectionApproval))
{
switch (inc.MessageType)
{
case NetIncomingMessageType.Data:
HandleDataMessage(inc);
break;
case NetIncomingMessageType.StatusChanged:
HandleStatusChanged(inc);
break;
}
}
}
catch (Exception e)
{
string errorMsg = "Server failed to read an incoming message. {" + e + "}\n" + e.StackTrace;
GameAnalyticsManager.AddErrorEventOnce("LidgrenServerPeer.Update:ClientReadException" + e.TargetSite.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
#if DEBUG
DebugConsole.ThrowError(errorMsg);
#else
if (GameSettings.VerboseLogging) { DebugConsole.ThrowError(errorMsg); }
#endif
}
for (int i = 0; i < pendingClients.Count; i++)
{
PendingClient pendingClient = pendingClients[i];
UpdatePendingClient(pendingClient, deltaTime);
if (i >= pendingClients.Count || pendingClients[i] != pendingClient) { i--; }
}
incomingLidgrenMessages.Clear();
}
private void InitUPnP()
{
if (netServer == null) { return; }
netServer.UPnP.ForwardPort(netPeerConfiguration.Port, "barotrauma");
if (Steam.SteamManager.USE_STEAM)
{
netServer.UPnP.ForwardPort(serverSettings.QueryPort, "barotrauma");
}
}
private bool DiscoveringUPnP()
{
if (netServer == null) { return false; }
return netServer.UPnP.Status == UPnPStatus.Discovering;
}
private void FinishUPnP()
{
//do nothing
}
private void HandleConnection(NetIncomingMessage inc)
{
if (netServer == null) { return; }
if (connectedClients.Count >= serverSettings.MaxPlayers)
{
inc.SenderConnection.Deny(DisconnectReason.ServerFull.ToString());
return;
}
if (serverSettings.BanList.IsBanned(inc.SenderConnection.RemoteEndPoint.Address, 0))
{
//IP banned: deny immediately
//TODO: use TextManager
inc.SenderConnection.Deny(DisconnectReason.Banned.ToString()+"/ IP banned");
return;
}
PendingClient pendingClient = pendingClients.Find(c => c.Connection == inc.SenderConnection);
if (pendingClient == null)
{
pendingClient = new PendingClient(inc.SenderConnection);
pendingClients.Add(pendingClient);
}
inc.SenderConnection.Approve();
}
private void HandleDataMessage(NetIncomingMessage inc)
{
if (netServer == null) { return; }
PendingClient pendingClient = pendingClients.Find(c => c.Connection == inc.SenderConnection);
byte incByte = inc.ReadByte();
bool isCompressed = (incByte & (byte)PacketHeader.IsCompressed) != 0;
bool isConnectionInitializationStep = (incByte & (byte)PacketHeader.IsConnectionInitializationStep) != 0;
if (isConnectionInitializationStep && pendingClient != null)
{
ReadConnectionInitializationStep(pendingClient, inc);
}
else if (!isConnectionInitializationStep)
{
LidgrenConnection conn = connectedClients.Find(c => c.NetConnection == inc.SenderConnection);
if (conn == null)
{
if (pendingClient != null)
{
RemovePendingClient(pendingClient, DisconnectReason.AuthenticationRequired.ToString()+"/ Received data message from unauthenticated client");
}
else if (inc.SenderConnection.Status != NetConnectionStatus.Disconnected &&
inc.SenderConnection.Status != NetConnectionStatus.Disconnecting)
{
inc.SenderConnection.Disconnect(DisconnectReason.AuthenticationRequired.ToString() + "/ Received data message from unauthenticated client");
}
return;
}
if (pendingClient != null) { pendingClients.Remove(pendingClient); }
if (serverSettings.BanList.IsBanned(conn.IPEndPoint.Address, conn.SteamID))
{
Disconnect(conn, DisconnectReason.Banned.ToString()+"/ Received data message from banned client");
return;
}
UInt16 length = inc.ReadUInt16();
//DebugConsole.NewMessage(isCompressed + " " + isConnectionInitializationStep + " " + (int)incByte + " " + length);
IReadMessage msg = new ReadOnlyMessage(inc.Data, isCompressed, inc.PositionInBytes, length, conn);
OnMessageReceived?.Invoke(conn, msg);
}
}
private void HandleStatusChanged(NetIncomingMessage inc)
{
if (netServer == null) { return; }
switch (inc.SenderConnection.Status)
{
case NetConnectionStatus.Disconnected:
string disconnectMsg;
LidgrenConnection conn = connectedClients.Find(c => c.NetConnection == inc.SenderConnection);
if (conn != null)
{
if (conn == OwnerConnection)
{
DebugConsole.NewMessage("Owner disconnected: closing the server...");
GameServer.Log("Owner disconnected: closing the server...", ServerLog.MessageType.ServerMessage);
Close(DisconnectReason.ServerShutdown.ToString() + "/ Owner disconnected");
}
else
{
disconnectMsg = $"ServerMessage.HasDisconnected~[client]={conn.Name}";
Disconnect(conn, disconnectMsg);
}
}
else
{
PendingClient pendingClient = pendingClients.Find(c => c.Connection == inc.SenderConnection);
if (pendingClient != null)
{
disconnectMsg = $"ServerMessage.HasDisconnected~[client]={pendingClient.Name}";
RemovePendingClient(pendingClient, disconnectMsg);
}
}
break;
}
}
private void ReadConnectionInitializationStep(PendingClient pendingClient, NetIncomingMessage inc)
{
if (netServer == null) { return; }
pendingClient.TimeOut = 20.0;
ConnectionInitialization initializationStep = (ConnectionInitialization)inc.ReadByte();
//DebugConsole.NewMessage(initializationStep+" "+pendingClient.InitializationStep);
if (pendingClient.InitializationStep != initializationStep) return;
switch (initializationStep)
{
case ConnectionInitialization.SteamTicketAndVersion:
string name = Client.SanitizeName(inc.ReadString());
int ownKey = inc.ReadInt32();
UInt64 steamId = inc.ReadUInt64();
UInt16 ticketLength = inc.ReadUInt16();
byte[] ticket = inc.ReadBytes(ticketLength);
if (!Client.IsValidName(name, serverSettings))
{
if (OwnerConnection != null ||
!IPAddress.IsLoopback(pendingClient.Connection.RemoteEndPoint.Address.MapToIPv4()) &&
ownerKey == null || ownKey == 0 && ownKey != ownerKey)
{
RemovePendingClient(pendingClient, DisconnectReason.InvalidName.ToString() + "/ The name \"" + name + "\" is invalid");
return;
}
}
string version = inc.ReadString();
bool isCompatibleVersion = NetworkMember.IsCompatible(version, GameMain.Version.ToString()) ?? false;
if (!isCompatibleVersion)
{
RemovePendingClient(pendingClient,
$"DisconnectMessage.InvalidVersion~[version]={GameMain.Version.ToString()}~[clientversion]={version}");
GameServer.Log(name + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (incompatible game version)", ServerLog.MessageType.Error);
DebugConsole.NewMessage(name + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (incompatible game version)", Microsoft.Xna.Framework.Color.Red);
return;
}
Int32 contentPackageCount = inc.ReadVariableInt32();
List<ClientContentPackage> contentPackages = new List<ClientContentPackage>();
for (int i = 0; i < contentPackageCount; i++)
{
string packageName = inc.ReadString();
string packageHash = inc.ReadString();
contentPackages.Add(new ClientContentPackage(packageName, packageHash));
}
List<ContentPackage> missingPackages = new List<ContentPackage>();
foreach (ContentPackage contentPackage in GameMain.SelectedPackages)
{
if (!contentPackage.HasMultiplayerIncompatibleContent) continue;
bool packageFound = false;
for (int i = 0; i < contentPackageCount; i++)
{
if (contentPackages[i].Name == contentPackage.Name && contentPackages[i].Hash == contentPackage.MD5hash.Hash)
{
packageFound = true;
break;
}
}
if (!packageFound) missingPackages.Add(contentPackage);
}
if (missingPackages.Count == 1)
{
RemovePendingClient(pendingClient,
$"DisconnectMessage.MissingContentPackage~[missingcontentpackage]={GetPackageStr(missingPackages[0])}");
GameServer.Log(name + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (missing content package " + GetPackageStr(missingPackages[0]) + ")", ServerLog.MessageType.Error);
return;
}
else if (missingPackages.Count > 1)
{
List<string> packageStrs = new List<string>();
missingPackages.ForEach(cp => packageStrs.Add(GetPackageStr(cp)));
RemovePendingClient(pendingClient,
$"DisconnectMessage.MissingContentPackages~[missingcontentpackages]={string.Join(", ", packageStrs)}");
GameServer.Log(name + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (missing content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error);
return;
}
if (pendingClient.SteamID == null)
{
bool requireSteamAuth = GameMain.Config.RequireSteamAuthentication;
#if DEBUG
requireSteamAuth = false;
#endif
//steam auth cannot be done (SteamManager not initialized or no ticket given),
//but it's not required either -> let the client join without auth
if ((!Steam.SteamManager.IsInitialized || ticket.Length == 0) &&
!requireSteamAuth)
{
pendingClient.Name = name;
pendingClient.OwnerKey = ownKey;
pendingClient.InitializationStep = ConnectionInitialization.Success;
}
else
{
ServerAuth.StartAuthSessionResult authSessionStartState = Steam.SteamManager.StartAuthSession(ticket, steamId);
if (authSessionStartState != ServerAuth.StartAuthSessionResult.OK)
{
RemovePendingClient(pendingClient, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam auth session failed to start: " + authSessionStartState.ToString());
return;
}
pendingClient.SteamID = steamId;
pendingClient.Name = name;
pendingClient.OwnerKey = ownKey;
pendingClient.AuthSessionStarted = true;
}
}
else //TODO: could remove since this seems impossible
{
if (pendingClient.SteamID != steamId)
{
RemovePendingClient(pendingClient, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ SteamID mismatch");
return;
}
}
break;
case ConnectionInitialization.Password:
int pwLength = inc.ReadByte();
byte[] incPassword = new byte[pwLength];
inc.ReadBytes(incPassword, 0, pwLength);
if (pendingClient.PasswordSalt == null)
{
DebugConsole.ThrowError("Received password message from client without salt");
return;
}
if (serverSettings.IsPasswordCorrect(incPassword, pendingClient.PasswordSalt.Value))
{
pendingClient.InitializationStep = ConnectionInitialization.Success;
}
else
{
pendingClient.Retries++;
if (pendingClient.Retries >= 3)
{
string banMsg = "Failed to enter correct password too many times";
if (pendingClient.SteamID != null)
{
serverSettings.BanList.BanPlayer(pendingClient.Name, pendingClient.SteamID.Value, banMsg, null);
}
serverSettings.BanList.BanPlayer(pendingClient.Name, pendingClient.Connection.RemoteEndPoint.Address, banMsg, null);
RemovePendingClient(pendingClient, DisconnectReason.Banned.ToString()+" /"+banMsg);
return;
}
}
pendingClient.UpdateTime = Timing.TotalTime;
break;
}
}
protected struct ClientContentPackage
{
public string Name;
public string Hash;
public ClientContentPackage(string name, string hash)
{
Name = name; Hash = hash;
}
}
private string GetPackageStr(ContentPackage contentPackage)
{
return "\"" + contentPackage.Name + "\" (hash " + contentPackage.MD5hash.ShortHash + ")";
}
private void UpdatePendingClient(PendingClient pendingClient, float deltaTime)
{
if (netServer == null) { return; }
if (serverSettings.BanList.IsBanned(pendingClient.Connection.RemoteEndPoint.Address, pendingClient.SteamID ?? 0))
{
RemovePendingClient(pendingClient, DisconnectReason.Banned.ToString());
return;
}
//DebugConsole.NewMessage("pending client status: " + pendingClient.InitializationStep);
if (connectedClients.Count >= serverSettings.MaxPlayers)
{
RemovePendingClient(pendingClient, DisconnectReason.ServerFull.ToString());
}
if (pendingClient.InitializationStep == ConnectionInitialization.Success)
{
LidgrenConnection newConnection = new LidgrenConnection(pendingClient.Name, pendingClient.Connection, pendingClient.SteamID ?? 0);
newConnection.Status = NetworkConnectionStatus.Connected;
connectedClients.Add(newConnection);
pendingClients.Remove(pendingClient);
if (OwnerConnection == null &&
IPAddress.IsLoopback(pendingClient.Connection.RemoteEndPoint.Address.MapToIPv4()) &&
ownerKey != null && pendingClient.OwnerKey != 0 && pendingClient.OwnerKey == ownerKey)
{
ownerKey = null;
OwnerConnection = newConnection;
}
OnInitializationComplete?.Invoke(newConnection);
}
pendingClient.TimeOut -= deltaTime;
if (pendingClient.TimeOut < 0.0)
{
RemovePendingClient(pendingClient, Lidgren.Network.NetConnection.NoResponseMessage);
}
if (Timing.TotalTime < pendingClient.UpdateTime) { return; }
pendingClient.UpdateTime = Timing.TotalTime + 1.0;
NetOutgoingMessage outMsg = netServer.CreateMessage();
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
outMsg.Write((byte)pendingClient.InitializationStep);
switch (pendingClient.InitializationStep)
{
case ConnectionInitialization.Password:
outMsg.Write(pendingClient.PasswordSalt == null); outMsg.WritePadBits();
if (pendingClient.PasswordSalt == null)
{
pendingClient.PasswordSalt = CryptoRandom.Instance.Next();
outMsg.Write(pendingClient.PasswordSalt.Value);
}
else
{
outMsg.Write(pendingClient.Retries);
}
break;
}
NetSendResult result = netServer.SendMessage(outMsg, pendingClient.Connection, NetDeliveryMethod.ReliableUnordered);
//DebugConsole.NewMessage("sent update to pending client: "+result);
}
private void RemovePendingClient(PendingClient pendingClient, string reason)
{
if (netServer == null) { return; }
if (pendingClients.Contains(pendingClient))
{
pendingClients.Remove(pendingClient);
if (pendingClient.AuthSessionStarted)
{
Steam.SteamManager.StopAuthSession(pendingClient.SteamID.Value);
pendingClient.SteamID = null;
pendingClient.AuthSessionStarted = false;
}
pendingClient.Connection.Disconnect(reason);
}
}
public override void InitializeSteamServerCallbacks(Server steamSrvr)
{
steamServer = steamSrvr;
steamServer.Auth.OnAuthChange = OnAuthChange;
}
private void OnAuthChange(ulong steamID, ulong ownerID, ServerAuth.Status status)
{
if (netServer == null) { return; }
PendingClient pendingClient = pendingClients.Find(c => c.SteamID == steamID);
DebugConsole.NewMessage(steamID + " validation: " + status+", "+(pendingClient!=null));
if (pendingClient == null)
{
if (status != ServerAuth.Status.OK)
{
LidgrenConnection connection = connectedClients.Find(c => c.SteamID == steamID);
if (connection != null)
{
Disconnect(connection, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam authentication status changed: " + status.ToString());
}
}
return;
}
if (serverSettings.BanList.IsBanned(pendingClient.Connection.RemoteEndPoint.Address, steamID))
{
RemovePendingClient(pendingClient, DisconnectReason.Banned.ToString() + "/ SteamID banned");
return;
}
if (status == ServerAuth.Status.OK)
{
pendingClient.InitializationStep = serverSettings.HasPassword ? ConnectionInitialization.Password : ConnectionInitialization.Success;
pendingClient.UpdateTime = Timing.TotalTime;
}
else
{
RemovePendingClient(pendingClient, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam authentication failed: " + status.ToString());
return;
}
}
public override void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod)
{
if (netServer == null) { return; }
if (!(conn is LidgrenConnection lidgrenConn)) return;
if (!connectedClients.Contains(lidgrenConn))
{
DebugConsole.ThrowError("Tried to send message to unauthenticated connection: " + lidgrenConn.IPString);
return;
}
NetDeliveryMethod lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable;
switch (deliveryMethod)
{
case DeliveryMethod.Unreliable:
lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable;
break;
case DeliveryMethod.Reliable:
lidgrenDeliveryMethod = NetDeliveryMethod.ReliableUnordered;
break;
case DeliveryMethod.ReliableOrdered:
lidgrenDeliveryMethod = NetDeliveryMethod.ReliableOrdered;
break;
}
NetOutgoingMessage lidgrenMsg = netServer.CreateMessage();
byte[] msgData = new byte[msg.LengthBytes];
msg.PrepareForSending(ref msgData, out bool isCompressed, out int length);
lidgrenMsg.Write((byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None));
lidgrenMsg.Write((UInt16)length);
lidgrenMsg.Write(msgData, 0, length);
netServer.SendMessage(lidgrenMsg, lidgrenConn.NetConnection, lidgrenDeliveryMethod);
}
public override void Disconnect(NetworkConnection conn,string msg=null)
{
if (netServer == null) { return; }
if (!(conn is LidgrenConnection lidgrenConn)) { return; }
if (connectedClients.Contains(lidgrenConn))
{
lidgrenConn.Status = NetworkConnectionStatus.Disconnected;
connectedClients.Remove(lidgrenConn);
OnDisconnect?.Invoke(conn, msg);
Steam.SteamManager.StopAuthSession(conn.SteamID);
}
lidgrenConn.NetConnection.Disconnect(msg ?? "Disconnected");
}
}
}
@@ -0,0 +1,34 @@
using Facepunch.Steamworks;
using System;
using System.Collections.Generic;
using System.Text;
namespace Barotrauma.Networking
{
abstract class ServerPeer
{
public delegate void MessageCallback(NetworkConnection connection, IReadMessage message);
public delegate void DisconnectCallback(NetworkConnection connection, string reason);
public delegate void InitializationCompleteCallback(NetworkConnection connection);
public delegate void ShutdownCallback();
public delegate void OwnerDeterminedCallback(NetworkConnection connection);
public MessageCallback OnMessageReceived;
public DisconnectCallback OnDisconnect;
public InitializationCompleteCallback OnInitializationComplete;
public ShutdownCallback OnShutdown;
public OwnerDeterminedCallback OnOwnerDetermined;
protected int? ownerKey;
public NetworkConnection OwnerConnection { get; protected set; }
public abstract void InitializeSteamServerCallbacks(Facepunch.Steamworks.Server steamSrvr);
public abstract void Start();
public abstract void Close(string msg = null);
public abstract void Update(float deltaTime);
public abstract void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod);
public abstract void Disconnect(NetworkConnection conn, string msg = null);
}
}
@@ -0,0 +1,652 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Linq;
using System.Threading;
using Lidgren.Network;
using Facepunch.Steamworks;
namespace Barotrauma.Networking
{
class SteamP2PServerPeer : ServerPeer
{
private ServerSettings serverSettings;
private NetPeerConfiguration netPeerConfiguration;
private NetServer netServer;
private NetConnection netConnection;
public UInt64 OwnerSteamID
{
get;
private set;
}
private class PendingClient
{
public string Name;
public ConnectionInitialization InitializationStep;
public double UpdateTime;
public double TimeOut;
public int Retries;
public UInt64 SteamID;
public Int32? PasswordSalt;
public bool AuthSessionStarted;
public PendingClient(UInt64 steamId)
{
InitializationStep = ConnectionInitialization.SteamTicketAndVersion;
Retries = 0;
SteamID = steamId;
PasswordSalt = null;
UpdateTime = Timing.TotalTime;
TimeOut = 20.0;
AuthSessionStarted = false;
}
public void Heartbeat()
{
TimeOut = 5.0;
}
}
private List<SteamP2PConnection> connectedClients;
private List<PendingClient> pendingClients;
private List<NetIncomingMessage> incomingLidgrenMessages;
public SteamP2PServerPeer(UInt64 steamId, ServerSettings settings)
{
serverSettings = settings;
netServer = null;
connectedClients = new List<SteamP2PConnection>();
pendingClients = new List<PendingClient>();
incomingLidgrenMessages = new List<NetIncomingMessage>();
ownerKey = null;
OwnerSteamID = steamId;
}
public override void Start()
{
if (netServer != null) { return; }
netPeerConfiguration = new NetPeerConfiguration("barotrauma")
{
AcceptIncomingConnections = true,
AutoExpandMTU = false,
MaximumConnections = 1, //only allow owner to connect
EnableUPnP = false,
Port = Steam.SteamManager.STEAMP2P_OWNER_PORT
};
netPeerConfiguration.DisableMessageType(NetIncomingMessageType.DebugMessage |
NetIncomingMessageType.WarningMessage | NetIncomingMessageType.Receipt |
NetIncomingMessageType.ErrorMessage | NetIncomingMessageType.Error |
NetIncomingMessageType.UnconnectedData);
netPeerConfiguration.EnableMessageType(NetIncomingMessageType.ConnectionApproval);
netServer = new NetServer(netPeerConfiguration);
netServer.Start();
}
public override void Close(string msg = null)
{
if (netServer == null) { return; }
if (OwnerConnection != null) OwnerConnection.Status = NetworkConnectionStatus.Disconnected;
for (int i = pendingClients.Count - 1; i >= 0; i--)
{
RemovePendingClient(pendingClients[i], msg ?? DisconnectReason.ServerShutdown.ToString());
}
for (int i = connectedClients.Count - 1; i >= 0; i--)
{
Disconnect(connectedClients[i], msg ?? DisconnectReason.ServerShutdown.ToString());
}
netServer.Shutdown(msg ?? DisconnectReason.ServerShutdown.ToString());
pendingClients.Clear();
connectedClients.Clear();
netServer = null;
OnShutdown?.Invoke();
}
public override void Update(float deltaTime)
{
if (netServer == null) { return; }
if (OnOwnerDetermined != null && OwnerConnection != null)
{
OnOwnerDetermined?.Invoke(OwnerConnection);
OnOwnerDetermined = null;
}
netServer.ReadMessages(incomingLidgrenMessages);
//backwards for loop so we can remove elements while iterating
for (int i = connectedClients.Count - 1; i >= 0; i--)
{
connectedClients[i].Decay(deltaTime);
if (connectedClients[i].Timeout < 0.0)
{
Disconnect(connectedClients[i], "Timed out");
}
}
//process incoming connections first
foreach (NetIncomingMessage inc in incomingLidgrenMessages.Where(m => m.MessageType == NetIncomingMessageType.ConnectionApproval))
{
HandleConnection(inc);
}
try
{
//after processing connections, go ahead with the rest of the messages
foreach (NetIncomingMessage inc in incomingLidgrenMessages.Where(m => m.MessageType != NetIncomingMessageType.ConnectionApproval))
{
switch (inc.MessageType)
{
case NetIncomingMessageType.Data:
HandleDataMessage(inc);
break;
case NetIncomingMessageType.StatusChanged:
HandleStatusChanged(inc);
break;
}
}
}
catch (Exception e)
{
string errorMsg = "Server failed to read an incoming message. {" + e + "}\n" + e.StackTrace;
GameAnalyticsManager.AddErrorEventOnce("SteamP2PServerPeer.Update:ClientReadException" + e.TargetSite.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
#if DEBUG
DebugConsole.ThrowError(errorMsg);
#else
if (GameSettings.VerboseLogging) { DebugConsole.ThrowError(errorMsg); }
#endif
}
for (int i = 0; i < pendingClients.Count; i++)
{
PendingClient pendingClient = pendingClients[i];
UpdatePendingClient(pendingClient);
if (i >= pendingClients.Count || pendingClients[i] != pendingClient) { i--; }
}
incomingLidgrenMessages.Clear();
}
private void HandleConnection(NetIncomingMessage inc)
{
if (netServer == null) { return; }
if (netConnection != null && inc.SenderConnection != netConnection)
{
inc.SenderConnection.Deny(DisconnectReason.SessionTaken.ToString()+"/ Owner is already connected");
return;
}
if (IPAddress.IsLoopback(inc.SenderConnection.RemoteEndPoint.Address.MapToIPv4()))
{
inc.SenderConnection.Approve();
netConnection = inc.SenderConnection;
return;
}
inc.SenderConnection.Deny(DisconnectReason.Kicked.ToString()+"/ Incoming connection is not loopback");
}
private void HandleDataMessage(NetIncomingMessage inc)
{
if (netServer == null) { return; }
if (inc.SenderConnection != netConnection) { return; }
UInt64 senderSteamId = inc.ReadUInt64();
byte incByte = inc.ReadByte();
bool isCompressed = (incByte & (byte)PacketHeader.IsCompressed) != 0;
bool isConnectionInitializationStep = (incByte & (byte)PacketHeader.IsConnectionInitializationStep) != 0;
bool isDisconnectMessage = (incByte & (byte)PacketHeader.IsDisconnectMessage) != 0;
bool isServerMessage = (incByte & (byte)PacketHeader.IsServerMessage) != 0;
bool isHeartbeatMessage = (incByte & (byte)PacketHeader.IsHeartbeatMessage) != 0;
if (isServerMessage)
{
DebugConsole.ThrowError("got server message from" + senderSteamId.ToString());
return;
}
if (senderSteamId != OwnerSteamID) //sender is remote, handle disconnects and heartbeats
{
PendingClient pendingClient = pendingClients.Find(c => c.SteamID == senderSteamId);
SteamP2PConnection connectedClient = connectedClients.Find(c => c.SteamID == senderSteamId);
pendingClient?.Heartbeat();
connectedClient?.Heartbeat();
if (serverSettings.BanList.IsBanned(senderSteamId))
{
if (pendingClient != null)
{
RemovePendingClient(pendingClient, DisconnectReason.Banned.ToString()+"/ Banned");
}
else if (connectedClient != null)
{
Disconnect(connectedClient, DisconnectReason.Banned.ToString() + "/ Banned");
}
return;
}
else if (isDisconnectMessage)
{
if (pendingClient != null)
{
string disconnectMsg = $"ServerMessage.HasDisconnected~[client]={pendingClient.Name}";
RemovePendingClient(pendingClient, disconnectMsg);
}
else if (connectedClient != null)
{
string disconnectMsg = $"ServerMessage.HasDisconnected~[client]={connectedClient.Name}";
Disconnect(connectedClient, disconnectMsg, false);
}
return;
}
else if (isHeartbeatMessage)
{
//message exists solely as a heartbeat, ignore its contents
return;
}
else if (isConnectionInitializationStep)
{
if (pendingClient != null)
{
ReadConnectionInitializationStep(pendingClient, new ReadOnlyMessage(inc.Data, false, inc.PositionInBytes, inc.LengthBytes - inc.PositionInBytes, null));
}
else
{
ConnectionInitialization initializationStep = (ConnectionInitialization)inc.ReadByte();
if (initializationStep == ConnectionInitialization.ConnectionStarted)
{
pendingClients.Add(new PendingClient(senderSteamId));
}
}
}
else if (connectedClient != null)
{
UInt16 length = inc.ReadUInt16();
IReadMessage msg = new ReadOnlyMessage(inc.Data, isCompressed, inc.PositionInBytes, length, connectedClient);
OnMessageReceived?.Invoke(connectedClient, msg);
}
}
else //sender is owner
{
if (OwnerConnection != null) { (OwnerConnection as SteamP2PConnection).Heartbeat(); }
if (isDisconnectMessage)
{
DebugConsole.ThrowError("Received disconnect message from owner");
return;
}
if (isServerMessage)
{
DebugConsole.ThrowError("Received server message from owner");
return;
}
if (isConnectionInitializationStep)
{
if (OwnerConnection == null)
{
string ownerName = inc.ReadString();
OwnerConnection = new SteamP2PConnection(ownerName, OwnerSteamID);
OwnerConnection.Status = NetworkConnectionStatus.Connected;
OnInitializationComplete?.Invoke(OwnerConnection);
}
return;
}
if (isHeartbeatMessage)
{
return;
}
else
{
UInt16 length = inc.ReadUInt16();
IReadMessage msg = new ReadOnlyMessage(inc.Data, isCompressed, inc.PositionInBytes, length, OwnerConnection);
OnMessageReceived?.Invoke(OwnerConnection, msg);
}
}
}
private void HandleStatusChanged(NetIncomingMessage inc)
{
if (netServer == null) { return; }
DebugConsole.NewMessage(inc.SenderConnection.Status.ToString());
switch (inc.SenderConnection.Status)
{
case NetConnectionStatus.Connected:
NetOutgoingMessage outMsg = netServer.CreateMessage();
outMsg.Write(OwnerSteamID);
outMsg.Write((byte)(PacketHeader.IsConnectionInitializationStep | PacketHeader.IsServerMessage));
netServer.SendMessage(outMsg, netConnection, NetDeliveryMethod.ReliableUnordered);
break;
case NetConnectionStatus.Disconnected:
DebugConsole.NewMessage("Owner disconnected: closing the server...");
GameServer.Log("Owner disconnected: closing the server...", ServerLog.MessageType.ServerMessage);
Close(DisconnectReason.ServerShutdown.ToString() + "/ Owner disconnected");
break;
}
}
private void ReadConnectionInitializationStep(PendingClient pendingClient, IReadMessage inc)
{
if (netServer == null) { return; }
pendingClient.TimeOut = 20.0;
ConnectionInitialization initializationStep = (ConnectionInitialization)inc.ReadByte();
//DebugConsole.NewMessage(initializationStep+" "+pendingClient.InitializationStep);
if (pendingClient.InitializationStep != initializationStep) return;
switch (initializationStep)
{
case ConnectionInitialization.SteamTicketAndVersion:
string name = Client.SanitizeName(inc.ReadString());
UInt64 steamId = inc.ReadUInt64();
UInt16 ticketLength = inc.ReadUInt16();
inc.BitPosition += ticketLength * 8; //skip ticket, owner handles steam authentication
if (!Client.IsValidName(name, serverSettings))
{
RemovePendingClient(pendingClient, DisconnectReason.InvalidName.ToString() + "/ The name \"" + name + "\" is invalid");
return;
}
string version = inc.ReadString();
bool isCompatibleVersion = NetworkMember.IsCompatible(version, GameMain.Version.ToString()) ?? false;
if (!isCompatibleVersion)
{
RemovePendingClient(pendingClient,
$"DisconnectMessage.InvalidVersion~[version]={GameMain.Version.ToString()}~[clientversion]={version}");
GameServer.Log(name + " (" + pendingClient.SteamID.ToString() + ") couldn't join the server (incompatible game version)", ServerLog.MessageType.Error);
DebugConsole.NewMessage(name + " (" + pendingClient.SteamID.ToString() + ") couldn't join the server (incompatible game version)", Microsoft.Xna.Framework.Color.Red);
return;
}
int contentPackageCount = (int)inc.ReadVariableUInt32();
List<ClientContentPackage> contentPackages = new List<ClientContentPackage>();
for (int i = 0; i < contentPackageCount; i++)
{
string packageName = inc.ReadString();
string packageHash = inc.ReadString();
contentPackages.Add(new ClientContentPackage(packageName, packageHash));
}
List<ContentPackage> missingPackages = new List<ContentPackage>();
foreach (ContentPackage contentPackage in GameMain.SelectedPackages)
{
if (!contentPackage.HasMultiplayerIncompatibleContent) continue;
bool packageFound = false;
for (int i = 0; i < (int)contentPackageCount; i++)
{
if (contentPackages[i].Name == contentPackage.Name && contentPackages[i].Hash == contentPackage.MD5hash.Hash)
{
packageFound = true;
break;
}
}
if (!packageFound) missingPackages.Add(contentPackage);
}
if (missingPackages.Count == 1)
{
RemovePendingClient(pendingClient,
$"DisconnectMessage.MissingContentPackage~[missingcontentpackage]={GetPackageStr(missingPackages[0])}");
GameServer.Log(name + " (" + pendingClient.SteamID.ToString() + ") couldn't join the server (missing content package " + GetPackageStr(missingPackages[0]) + ")", ServerLog.MessageType.Error);
return;
}
else if (missingPackages.Count > 1)
{
List<string> packageStrs = new List<string>();
missingPackages.ForEach(cp => packageStrs.Add(GetPackageStr(cp)));
RemovePendingClient(pendingClient,
$"DisconnectMessage.MissingContentPackages~[missingcontentpackages]={string.Join(", ", packageStrs)}");
GameServer.Log(name + " (" + pendingClient.SteamID.ToString() + ") couldn't join the server (missing content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error);
return;
}
if (!pendingClient.AuthSessionStarted)
{
pendingClient.InitializationStep = serverSettings.HasPassword ? ConnectionInitialization.Password: ConnectionInitialization.Success;
pendingClient.Name = name;
pendingClient.AuthSessionStarted = true;
}
break;
case ConnectionInitialization.Password:
int pwLength = inc.ReadByte();
byte[] incPassword = inc.ReadBytes(pwLength);
if (pendingClient.PasswordSalt == null)
{
DebugConsole.ThrowError("Received password message from client without salt");
return;
}
if (serverSettings.IsPasswordCorrect(incPassword, pendingClient.PasswordSalt.Value))
{
pendingClient.InitializationStep = ConnectionInitialization.Success;
}
else
{
pendingClient.Retries++;
if (pendingClient.Retries >= 3)
{
string banMsg = "Failed to enter correct password too many times";
serverSettings.BanList.BanPlayer(pendingClient.Name, pendingClient.SteamID, banMsg, null);
RemovePendingClient(pendingClient, DisconnectReason.Banned.ToString()+"/ "+banMsg);
return;
}
}
pendingClient.UpdateTime = Timing.TotalTime;
break;
}
}
protected struct ClientContentPackage
{
public string Name;
public string Hash;
public ClientContentPackage(string name, string hash)
{
Name = name; Hash = hash;
}
}
private string GetPackageStr(ContentPackage contentPackage)
{
return "\"" + contentPackage.Name + "\" (hash " + contentPackage.MD5hash.ShortHash + ")";
}
private void UpdatePendingClient(PendingClient pendingClient)
{
if (netServer == null) { return; }
if (serverSettings.BanList.IsBanned(pendingClient.SteamID))
{
RemovePendingClient(pendingClient, DisconnectReason.Banned.ToString()+"/ Initialization interrupted by ban");
return;
}
//DebugConsole.NewMessage("pending client status: " + pendingClient.InitializationStep);
if (connectedClients.Count >= serverSettings.MaxPlayers-1)
{
RemovePendingClient(pendingClient, DisconnectReason.ServerFull.ToString());
}
if (pendingClient.InitializationStep == ConnectionInitialization.Success)
{
SteamP2PConnection newConnection = new SteamP2PConnection(pendingClient.Name, pendingClient.SteamID);
newConnection.Status = NetworkConnectionStatus.Connected;
connectedClients.Add(newConnection);
pendingClients.Remove(pendingClient);
OnInitializationComplete?.Invoke(newConnection);
}
pendingClient.TimeOut -= Timing.Step;
if (pendingClient.TimeOut < 0.0)
{
RemovePendingClient(pendingClient, Lidgren.Network.NetConnection.NoResponseMessage);
}
if (Timing.TotalTime < pendingClient.UpdateTime) { return; }
pendingClient.UpdateTime = Timing.TotalTime + 1.0;
NetOutgoingMessage outMsg = netServer.CreateMessage();
outMsg.Write(pendingClient.SteamID);
outMsg.Write((byte)(PacketHeader.IsConnectionInitializationStep |
PacketHeader.IsServerMessage));
outMsg.Write((byte)pendingClient.InitializationStep);
switch (pendingClient.InitializationStep)
{
case ConnectionInitialization.Password:
outMsg.Write(pendingClient.PasswordSalt == null); outMsg.WritePadBits();
if (pendingClient.PasswordSalt == null)
{
pendingClient.PasswordSalt = CryptoRandom.Instance.Next();
outMsg.Write(pendingClient.PasswordSalt.Value);
}
else
{
outMsg.Write(pendingClient.Retries);
}
break;
}
if (netConnection != null)
{
NetSendResult result = netServer.SendMessage(outMsg, netConnection, NetDeliveryMethod.ReliableUnordered);
}
}
private void RemovePendingClient(PendingClient pendingClient, string reason)
{
if (netServer == null) { return; }
if (pendingClients.Contains(pendingClient))
{
SendDisconnectMessage(pendingClient.SteamID, reason);
pendingClients.Remove(pendingClient);
if (pendingClient.AuthSessionStarted)
{
Steam.SteamManager.StopAuthSession(pendingClient.SteamID);
pendingClient.SteamID = 0;
pendingClient.AuthSessionStarted = false;
}
}
}
public override void InitializeSteamServerCallbacks(Server steamSrvr)
{
throw new InvalidOperationException("Called InitializeSteamServerCallbacks on SteamP2PServerPeer!");
}
public override void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod)
{
if (netServer == null) { return; }
if (!(conn is SteamP2PConnection steamp2pConn)) return;
if (!connectedClients.Contains(steamp2pConn) && conn != OwnerConnection)
{
DebugConsole.ThrowError("Tried to send message to unauthenticated connection: " + steamp2pConn.SteamID.ToString());
return;
}
NetDeliveryMethod lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable;
switch (deliveryMethod)
{
case DeliveryMethod.Unreliable:
lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable;
break;
case DeliveryMethod.Reliable:
lidgrenDeliveryMethod = NetDeliveryMethod.ReliableUnordered;
break;
case DeliveryMethod.ReliableOrdered:
lidgrenDeliveryMethod = NetDeliveryMethod.ReliableOrdered;
break;
}
NetOutgoingMessage lidgrenMsg = netServer.CreateMessage();
byte[] msgData = new byte[1500];
msg.PrepareForSending(ref msgData, out bool isCompressed, out int length);
lidgrenMsg.Write(conn.SteamID);
lidgrenMsg.Write((byte)((isCompressed ? PacketHeader.IsCompressed : PacketHeader.None) | PacketHeader.IsServerMessage));
lidgrenMsg.Write((UInt16)length);
lidgrenMsg.Write(msgData, 0, length);
netServer.SendMessage(lidgrenMsg, netConnection, lidgrenDeliveryMethod);
}
private void SendDisconnectMessage(UInt64 steamId, string msg)
{
if (netServer == null) { return; }
if (string.IsNullOrWhiteSpace(msg)) { return; }
NetOutgoingMessage lidgrenMsg = netServer.CreateMessage();
lidgrenMsg.Write(steamId);
lidgrenMsg.Write((byte)(PacketHeader.IsDisconnectMessage | PacketHeader.IsServerMessage));
lidgrenMsg.Write(msg);
netServer.SendMessage(lidgrenMsg, netConnection, NetDeliveryMethod.ReliableUnordered);
}
private void Disconnect(NetworkConnection conn, string msg, bool sendDisconnectMessage)
{
if (netServer == null) { return; }
if (!(conn is SteamP2PConnection steamp2pConn)) { return; }
if (connectedClients.Contains(steamp2pConn))
{
if (sendDisconnectMessage) SendDisconnectMessage(steamp2pConn.SteamID, msg);
steamp2pConn.Status = NetworkConnectionStatus.Disconnected;
connectedClients.Remove(steamp2pConn);
OnDisconnect?.Invoke(conn, msg);
Steam.SteamManager.StopAuthSession(conn.SteamID);
}
else if (steamp2pConn == OwnerConnection)
{
netConnection.Disconnect(msg);
}
}
public override void Disconnect(NetworkConnection conn, string msg = null)
{
Disconnect(conn, msg, true);
}
}
}
@@ -27,8 +27,8 @@ namespace Barotrauma.Networking
.ToList();
}
int currPlayerCount = GameMain.Server.ConnectedClients.Count(c =>
c.InGame &&
int currPlayerCount = GameMain.Server.ConnectedClients.Count(c =>
c.InGame &&
(!c.SpectateOnly || (!GameMain.Server.ServerSettings.AllowSpectating && GameMain.Server.OwnerConnection != c.Connection)));
var existingBots = Character.CharacterList
@@ -68,7 +68,7 @@ namespace Barotrauma.Networking
{
RespawnCountdownStarted = respawnPending;
RespawnTime = DateTime.Now + new TimeSpan(0,0,0,0, (int)(GameMain.Server.ServerSettings.RespawnInterval * 1000.0f));
GameMain.Server.CreateEntityEvent(this);
GameMain.Server.CreateEntityEvent(this);
}
if (!RespawnCountdownStarted) { return; }
@@ -180,7 +180,7 @@ namespace Barotrauma.Networking
partial void UpdateTransportingProjSpecific(float deltaTime)
{
if (!ReturnCountdownStarted)
{
//if there are no living chracters inside, transporting can be stopped immediately
@@ -231,7 +231,7 @@ namespace Barotrauma.Networking
var botsToSpawn = GetBotsToRespawn();
characterInfos.AddRange(botsToSpawn);
GameMain.Server.AssignJobs(clients);
foreach (Client c in clients)
{
@@ -257,7 +257,7 @@ namespace Barotrauma.Networking
var character = Character.Create(characterInfos[i], shuttleSpawnPoints[i].WorldPosition, characterInfos[i].Name, !bot, bot);
character.TeamID = Character.TeamType.Team1;
if (bot)
{
GameServer.Log(string.Format("Respawning bot {0} as {1}", character.Info.Name, characterInfos[i].Job.Name), ServerLog.MessageType.Spawning);
@@ -265,18 +265,18 @@ namespace Barotrauma.Networking
else
{
//tell the respawning client they're no longer a traitor
if (GameMain.Server.TraitorManager != null && clients[i].Character != null)
if (GameMain.Server.TraitorManager?.Traitors != null && clients[i].Character != null)
{
if (GameMain.Server.TraitorManager.TraitorList.Any(t => t.Character == clients[i].Character))
if (GameMain.Server.TraitorManager.Traitors.Any(t => t.Character == clients[i].Character))
{
GameMain.Server.SendDirectChatMessage(TextManager.Get("traitorrespawnmessage"), clients[i], ChatMessageType.MessageBox);
GameMain.Server.SendDirectChatMessage(TextManager.FormatServerMessage("TraitorRespawnMessage"), clients[i], ChatMessageType.ServerMessageBox);
}
}
clients[i].Character = character;
character.OwnerClientIP = clients[i].Connection.RemoteEndPoint.Address.ToString();
character.OwnerClientEndPoint = clients[i].Connection.EndPointString;
character.OwnerClientName = clients[i].Name;
GameServer.Log(string.Format("Respawning {0} ({1}) as {2}", clients[i].Name, clients[i].Connection?.RemoteEndPoint?.Address, characterInfos[i].Job.Name), ServerLog.MessageType.Spawning);
GameServer.Log(string.Format("Respawning {0} ({1}) as {2}", clients[i].Name, clients[i].Connection?.EndPointString, characterInfos[i].Job.Name), ServerLog.MessageType.Spawning);
}
if (divingSuitPrefab != null && oxyPrefab != null && RespawnShuttle != null)
@@ -325,9 +325,9 @@ namespace Barotrauma.Networking
}
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
msg.WriteRangedInteger(0, Enum.GetNames(typeof(State)).Length, (int)CurrentState);
msg.WriteRangedIntegerDeprecated(0, Enum.GetNames(typeof(State)).Length, (int)CurrentState);
switch (CurrentState)
{
@@ -1,5 +1,4 @@
using Lidgren.Network;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -20,7 +19,7 @@ namespace Barotrauma.Networking
LoadClientPermissions();
}
private void WriteNetProperties(NetBuffer outMsg)
private void WriteNetProperties(IWriteMessage outMsg)
{
outMsg.Write((UInt16)netProperties.Keys.Count);
foreach (UInt32 key in netProperties.Keys)
@@ -30,7 +29,7 @@ namespace Barotrauma.Networking
}
}
public void ServerAdminWrite(NetBuffer outMsg, Client c)
public void ServerAdminWrite(IWriteMessage outMsg, Client c)
{
//outMsg.Write(isPublic);
//outMsg.Write(EnableUPnP);
@@ -43,11 +42,15 @@ namespace Barotrauma.Networking
Whitelist.ServerAdminWrite(outMsg, c);
}
public void ServerWrite(NetBuffer outMsg,Client c)
public void ServerWrite(IWriteMessage outMsg,Client c)
{
outMsg.Write(ServerName);
outMsg.Write(ServerMessageText);
outMsg.WriteRangedInteger(1, 60, TickRate);
outMsg.Write((byte)MaxPlayers);
outMsg.Write(HasPassword);
outMsg.Write(isPublic);
outMsg.WritePadBits();
outMsg.WriteRangedIntegerDeprecated(1, 60, TickRate);
WriteExtraCargo(outMsg);
@@ -67,7 +70,7 @@ namespace Barotrauma.Networking
}
}
public void ServerRead(NetIncomingMessage incMsg,Client c)
public void ServerRead(IReadMessage incMsg,Client c)
{
if (!c.HasPermission(Networking.ClientPermissions.ManageSettings)) return;
@@ -112,7 +115,7 @@ namespace Barotrauma.Networking
else
{
UInt32 size = incMsg.ReadVariableUInt32();
incMsg.Position += 8 * size;
incMsg.BitPosition += (int)(8 * size);
}
}
@@ -145,7 +148,7 @@ namespace Barotrauma.Networking
if (botSpawnMode > 1) botSpawnMode = 0;
BotSpawnMode = (BotSpawnMode)botSpawnMode;
float levelDifficulty = incMsg.ReadFloat();
float levelDifficulty = incMsg.ReadSingle();
if (levelDifficulty >= 0.0f) SelectedLevelDifficulty = levelDifficulty;
UseRespawnShuttle = incMsg.ReadBoolean();
@@ -177,24 +180,16 @@ namespace Barotrauma.Networking
doc.Root.SetAttributeValue("name", ServerName);
doc.Root.SetAttributeValue("public", isPublic);
doc.Root.SetAttributeValue("port", GameMain.Server.NetPeerConfiguration.Port);
doc.Root.SetAttributeValue("port", Port);
if (Steam.SteamManager.USE_STEAM) doc.Root.SetAttributeValue("queryport", QueryPort);
doc.Root.SetAttributeValue("maxplayers", maxPlayers);
doc.Root.SetAttributeValue("enableupnp", GameMain.Server.NetPeerConfiguration.EnableUPnP);
doc.Root.SetAttributeValue("enableupnp", EnableUPnP);
doc.Root.SetAttributeValue("autorestart", autoRestart);
doc.Root.SetAttributeValue("SubSelection", SubSelectionMode.ToString());
doc.Root.SetAttributeValue("ModeSelection", ModeSelectionMode.ToString());
doc.Root.SetAttributeValue("LevelDifficulty", ((int)selectedLevelDifficulty).ToString());
doc.Root.SetAttributeValue("TraitorsEnabled", TraitorsEnabled.ToString());
/*doc.Root.SetAttributeValue("BotCount", BotCount);
doc.Root.SetAttributeValue("MaxBotCount", MaxBotCount);*/
doc.Root.SetAttributeValue("BotSpawnMode", BotSpawnMode.ToString());
doc.Root.SetAttributeValue("AllowedRandomMissionTypes", string.Join(",", AllowedRandomMissionTypes));
doc.Root.SetAttributeValue("AllowedClientNameChars", string.Join(",", AllowedClientNameChars.Select(c => c.First + "-" + c.Second)));
doc.Root.SetAttributeValue("ServerMessage", ServerMessageText);
@@ -239,18 +234,22 @@ namespace Barotrauma.Networking
selectedLevelDifficulty = doc.Root.GetAttributeFloat("LevelDifficulty", 20.0f);
GameMain.NetLobbyScreen.SetLevelDifficulty(selectedLevelDifficulty);
var traitorsEnabled = TraitorsEnabled;
Enum.TryParse(doc.Root.GetAttributeString("TraitorsEnabled", "No"), out traitorsEnabled);
TraitorsEnabled = traitorsEnabled;
GameMain.NetLobbyScreen.SetTraitorsEnabled(traitorsEnabled);
var botSpawnMode = BotSpawnMode.Normal;
Enum.TryParse(doc.Root.GetAttributeString("BotSpawnMode", "Normal"), out botSpawnMode);
BotSpawnMode = botSpawnMode;
//"65-90", "97-122", "48-59" = upper and lower case english alphabet and numbers
string[] allowedClientNameCharsStr = doc.Root.GetAttributeStringArray("AllowedClientNameChars", new string[] { "65-90", "97-122", "48-59" });
string[] allowedClientNameCharsStr = doc.Root.GetAttributeStringArray("AllowedClientNameChars",
new string[] {
"32-33",
"38-46",
"48-57",
"65-90",
"91",
"93",
"95-122",
"192-255",
"384-591",
"1024-1279"
});
foreach (string allowedClientNameCharRange in allowedClientNameCharsStr)
{
string[] splitRange = allowedClientNameCharRange.Split('-');
@@ -329,7 +328,7 @@ namespace Barotrauma.Networking
foreach (XElement clientElement in doc.Root.Elements())
{
string clientName = clientElement.GetAttributeString("name", "");
string clientIP = clientElement.GetAttributeString("ip", "");
string clientEndPoint = clientElement.GetAttributeString("endpoint", null) ?? clientElement.GetAttributeString("ip", "");
string steamIdStr = clientElement.GetAttributeString("steamid", "");
if (string.IsNullOrWhiteSpace(clientName))
@@ -337,7 +336,7 @@ namespace Barotrauma.Networking
DebugConsole.ThrowError("Error in " + ClientPermissionsFile + " - all clients must have a name and an IP address.");
continue;
}
if (string.IsNullOrWhiteSpace(clientIP) && string.IsNullOrWhiteSpace(steamIdStr))
if (string.IsNullOrWhiteSpace(clientEndPoint) && string.IsNullOrWhiteSpace(steamIdStr))
{
DebugConsole.ThrowError("Error in " + ClientPermissionsFile + " - all clients must have an IP address or a Steam ID.");
continue;
@@ -410,7 +409,7 @@ namespace Barotrauma.Networking
}
else
{
ClientPermissions.Add(new SavedClientPermission(clientName, clientIP, permissions, permittedCommands));
ClientPermissions.Add(new SavedClientPermission(clientName, clientEndPoint, permissions, permittedCommands));
}
}
}
@@ -480,7 +479,7 @@ namespace Barotrauma.Networking
}
else
{
clientElement.Add(new XAttribute("ip", clientPermission.IP));
clientElement.Add(new XAttribute("endpoint", clientPermission.EndPoint));
}
if (matchingPreset == null)
@@ -29,7 +29,8 @@ namespace Barotrauma.Steam
RefreshServerDetails(server);
instance.server.Auth.OnAuthChange = server.OnAuthChange;
server.ServerPeer.InitializeSteamServerCallbacks(instance.server);
Instance.server.LogOnAnonymous();
return true;
@@ -66,23 +67,24 @@ namespace Barotrauma.Steam
Instance.server.SetKey("traitors", server.ServerSettings.TraitorsEnabled.ToString());
Instance.server.SetKey("gamestarted", server.GameStarted.ToString());
Instance.server.SetKey("gamemode", server.ServerSettings.GameModeIdentifier);
instance.server.DedicatedServer = true;
return true;
}
public static bool StartAuthSession(byte[] authTicketData, ulong clientSteamID)
public static ServerAuth.StartAuthSessionResult StartAuthSession(byte[] authTicketData, ulong clientSteamID)
{
if (instance == null || !instance.isInitialized || instance.server == null) return false;
if (instance == null || !instance.isInitialized || instance.server == null) return ServerAuth.StartAuthSessionResult.ServerNotConnectedToSteam;
DebugConsole.Log("SteamManager authenticating Steam client " + clientSteamID);
if (!instance.server.Auth.StartSession(authTicketData, clientSteamID))
ServerAuth.StartAuthSessionResult startResult = instance.server.Auth.StartSession(authTicketData, clientSteamID);
if (startResult != ServerAuth.StartAuthSessionResult.OK)
{
DebugConsole.Log("Authentication failed");
return false;
DebugConsole.Log("Authentication failed: failed to start auth session (" + startResult.ToString() + ")");
}
return true;
return startResult;
}
public static void StopAuthSession(ulong clientSteamID)
@@ -1,5 +1,4 @@
using Barotrauma.Items.Components;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -9,11 +8,11 @@ namespace Barotrauma.Networking
{
class VoipServer
{
private NetServer netServer;
private ServerPeer netServer;
private List<VoipQueue> queues;
private Dictionary<VoipQueue,DateTime> lastSendTime;
public VoipServer(NetServer server)
public VoipServer(ServerPeer server)
{
this.netServer = server;
queues = new List<VoipQueue>();
@@ -54,15 +53,13 @@ namespace Barotrauma.Networking
if (!CanReceive(sender, recipient)) { continue; }
NetOutgoingMessage msg = netServer.CreateMessage();
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ServerPacketHeader.VOICE);
msg.Write((byte)queue.QueueID);
queue.Write(msg);
GameMain.Server.CompressOutgoingMessage(msg);
netServer.SendMessage(msg, recipient.Connection, NetDeliveryMethod.Unreliable);
netServer.Send(msg, recipient.Connection, DeliveryMethod.Unreliable);
}
}
}
@@ -1,5 +1,4 @@
using Barotrauma.Networking;
using Lidgren.Network;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -19,7 +18,7 @@ namespace Barotrauma
set { allowModeVoting = value; }
}
public void ServerRead(NetIncomingMessage inc, Client sender)
public void ServerRead(IReadMessage inc, Client sender)
{
if (GameMain.Server == null || sender == null) return;
@@ -86,7 +85,7 @@ namespace Barotrauma
GameMain.Server.UpdateVoteStatus();
}
public void ServerWrite(NetBuffer msg)
public void ServerWrite(IWriteMessage msg)
{
if (GameMain.Server == null) return;
@@ -1,5 +1,4 @@
using Lidgren.Network;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.IO;
@@ -127,7 +126,7 @@ namespace Barotrauma.Networking
whitelistedPlayers.Add(new WhiteListedPlayer(name, ip));
}
public void ServerAdminWrite(NetBuffer outMsg, Client c)
public void ServerAdminWrite(IWriteMessage outMsg, Client c)
{
if (!c.HasPermission(ClientPermissions.ManageSettings))
{
@@ -139,7 +138,7 @@ namespace Barotrauma.Networking
outMsg.Write(Enabled);
outMsg.WritePadBits();
outMsg.WriteVariableInt32(whitelistedPlayers.Count);
outMsg.WriteVariableUInt32((UInt32)whitelistedPlayers.Count);
for (int i = 0; i < whitelistedPlayers.Count; i++)
{
WhiteListedPlayer whitelistedPlayer = whitelistedPlayers[i];
@@ -154,13 +153,13 @@ namespace Barotrauma.Networking
}
}
public bool ServerAdminRead(NetBuffer incMsg, Client c)
public bool ServerAdminRead(IReadMessage incMsg, Client c)
{
if (!c.HasPermission(ClientPermissions.ManageSettings))
{
bool enabled = incMsg.ReadBoolean(); incMsg.ReadPadBits();
UInt16 removeCount = incMsg.ReadUInt16();
incMsg.Position += removeCount * 4 * 8;
incMsg.BitPosition += removeCount * 4 * 8;
UInt16 addCount = incMsg.ReadUInt16();
for (int i = 0; i < addCount; i++)
{