Unstable v0.19.3.0

This commit is contained in:
Juan Pablo Arce
2022-09-02 15:10:56 -03:00
parent 28789616bd
commit 3f2c843247
336 changed files with 7152 additions and 7739 deletions
@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework;
#nullable enable
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -24,36 +25,31 @@ namespace Barotrauma.Networking
partial class BanList
{
private GUIComponent banFrame;
public GUIComponent BanFrame
{
get { return banFrame; }
}
public GUIComponent? BanFrame { get; private set; }
public List<UInt32> localRemovedBans = new List<UInt32>();
private void RecreateBanFrame()
{
if (banFrame != null)
if (BanFrame != null)
{
var parent = banFrame.Parent;
parent.RemoveChild(banFrame);
var parent = BanFrame.Parent;
parent.RemoveChild(BanFrame);
CreateBanFrame(parent);
}
}
public GUIComponent CreateBanFrame(GUIComponent parent)
{
banFrame = new GUIListBox(new RectTransform(Vector2.One, parent.RectTransform, Anchor.Center));
BanFrame = new GUIListBox(new RectTransform(Vector2.One, parent.RectTransform, Anchor.Center));
foreach (BannedPlayer bannedPlayer in bannedPlayers)
{
if (localRemovedBans.Contains(bannedPlayer.UniqueIdentifier)) { continue; }
var playerFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), ((GUIListBox)banFrame).Content.RectTransform) { MinSize = new Point(0, 70) })
var playerFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), ((GUIListBox)BanFrame).Content.RectTransform) { MinSize = new Point(0, 70) })
{
UserData = banFrame
UserData = BanFrame
};
var paddedPlayerFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.85f), playerFrame.RectTransform, Anchor.Center))
@@ -102,16 +98,15 @@ namespace Barotrauma.Networking
paddedPlayerFrame.Recalculate();
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), ((GUIListBox)banFrame).Content.RectTransform), style: "HorizontalLine");
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), ((GUIListBox)BanFrame).Content.RectTransform), style: "HorizontalLine");
}
return banFrame;
return BanFrame;
}
private bool RemoveBan(GUIButton button, object obj)
{
BannedPlayer banned = obj as BannedPlayer;
if (banned == null) { return false; }
if (!(obj is BannedPlayer banned)) { return false; }
localRemovedBans.Add(banned.UniqueIdentifier);
RecreateBanFrame();
@@ -178,10 +173,10 @@ namespace Barotrauma.Networking
bannedPlayers.Add(new BannedPlayer(uniqueIdentifier, name, addressOrAccountId, reason, expiration));
}
if (banFrame != null)
if (BanFrame != null)
{
var parent = banFrame.Parent;
parent.RemoveChild(banFrame);
var parent = BanFrame.Parent;
parent.RemoveChild(BanFrame);
CreateBanFrame(parent);
}
}
@@ -191,7 +186,7 @@ namespace Barotrauma.Networking
outMsg.WriteVariableUInt32((UInt32)localRemovedBans.Count);
foreach (UInt32 uniqueId in localRemovedBans)
{
outMsg.Write(uniqueId);
outMsg.WriteUInt32(uniqueId);
}
localRemovedBans.Clear();
@@ -8,11 +8,11 @@ namespace Barotrauma.Networking
{
public virtual void ClientWrite(IWriteMessage msg)
{
msg.Write((byte)ClientNetObject.CHAT_MESSAGE);
msg.Write(NetStateID);
msg.WriteByte((byte)ClientNetObject.CHAT_MESSAGE);
msg.WriteUInt16(NetStateID);
msg.WriteRangedInteger((int)Type, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1);
msg.WriteRangedInteger((int)ChatMode, 0, Enum.GetValues(typeof(ChatMode)).Length - 1);
msg.Write(Text);
msg.WriteString(Text);
}
public static void ClientRead(IReadMessage msg)
@@ -12,7 +12,7 @@ namespace Barotrauma.Networking
class FileReceiver
{
public class FileTransferIn : IDisposable
{
{
public string FileName
{
get;
@@ -36,7 +36,7 @@ namespace Barotrauma.Networking
get;
private set;
}
public int LastSeen { get; set; }
public FileTransferType FileType
@@ -93,6 +93,12 @@ namespace Barotrauma.Networking
public int ID;
public const int DataBufferSize = 50;
/// <summary>
/// Data that we've ignored because we're waiting for some earlier data. Key = byte offset, value = the actual data
/// </summary>
public readonly Dictionary<int, byte[]> DataBuffer = new Dictionary<int, byte[]>();
public FileTransferIn(NetworkConnection connection, string filePath, FileTransferType fileType)
{
FilePath = filePath;
@@ -128,20 +134,25 @@ namespace Barotrauma.Networking
bytesToRead -= Received + bytesToRead - FileSize;
}
byte[] all = inc.ReadBytes(bytesToRead);
Received += all.Length;
WriteStream.Write(all, 0, all.Length);
ReadBytes(inc.ReadBytes(bytesToRead));
}
public void ReadBytes(byte[] data)
{
Received += data.Length;
WriteStream.Write(data, 0, data.Length);
int passed = Environment.TickCount - TimeStarted;
float psec = passed / 1000.0f;
if (GameSettings.CurrentConfig.VerboseLogging)
{
DebugConsole.Log($"Received {all.Length} bytes of the file {FileName} ({Received / 1000}/{FileSize / 1000} kB received)");
}
BytesPerSecond = Received / psec;
var outdatedKeys = DataBuffer.Keys.Where(k => k < Received).ToList();
foreach (int key in outdatedKeys)
{
DataBuffer.Remove(key);
}
Status = Received >= FileSize ? FileTransferStatus.Finished : FileTransferStatus.Receiving;
}
@@ -349,6 +360,10 @@ namespace Barotrauma.Networking
if (offset != activeTransfer.Received)
{
activeTransfer.LastSeen = Math.Max(offset, activeTransfer.LastSeen);
if (!activeTransfer.DataBuffer.ContainsKey(offset) && activeTransfer.DataBuffer.Count < FileTransferIn.DataBufferSize)
{
activeTransfer.DataBuffer.Add(offset, inc.ReadBytes(bytesToRead));
}
DebugConsole.Log($"Received {bytesToRead} bytes of the file {activeTransfer.FileName} (ignoring: offset {offset}, waiting for {activeTransfer.Received})");
GameMain.Client.UpdateFileTransfer(activeTransfer, activeTransfer.Received, activeTransfer.LastSeen);
return;
@@ -370,7 +385,16 @@ namespace Barotrauma.Networking
try
{
activeTransfer.ReadBytes(inc, bytesToRead);
activeTransfer.ReadBytes(inc, bytesToRead);
if (GameSettings.CurrentConfig.VerboseLogging)
{
DebugConsole.Log($"Received {bytesToRead} bytes of the file {activeTransfer.FileName} ({activeTransfer.Received / 1000}/{activeTransfer.FileSize / 1000} kB received)");
}
while (activeTransfer.DataBuffer.TryGetValue(activeTransfer.Received, out byte[] data))
{
activeTransfer.ReadBytes(data);
DebugConsole.Log($"Read {data.Length} bytes of buffer data of the file {activeTransfer.FileName} ({activeTransfer.Received / 1000}/{activeTransfer.FileSize / 1000} kB received)");
}
}
catch (Exception e)
{
@@ -434,7 +458,7 @@ namespace Barotrauma.Networking
}
if (string.IsNullOrEmpty(fileName) ||
fileName.IndexOfAny(Path.GetInvalidFileNameChars().ToArray()) > -1)
fileName.IndexOfAny(Path.GetInvalidFileNameCharsCrossPlatform().ToArray()) > -1)
{
errorMessage = "Illegal characters in file name ''" + fileName + "''";
return false;
@@ -470,7 +494,7 @@ namespace Barotrauma.Networking
System.IO.Stream stream;
try
{
stream = SaveUtil.DecompressFiletoStream(fileTransfer.FilePath);
stream = SaveUtil.DecompressFileToStream(fileTransfer.FilePath);
}
catch (Exception e)
{
@@ -674,13 +674,13 @@ namespace Barotrauma.Networking
{
case ServerPacketHeader.PING_REQUEST:
IWriteMessage response = new WriteOnlyMessage();
response.Write((byte)ClientPacketHeader.PING_RESPONSE);
response.WriteByte((byte)ClientPacketHeader.PING_RESPONSE);
byte requestLen = inc.ReadByte();
response.Write(requestLen);
response.WriteByte(requestLen);
for (int i = 0; i < requestLen; i++)
{
byte b = inc.ReadByte();
response.Write(b);
response.WriteByte(b);
}
clientPeer.Send(response, DeliveryMethod.Unreliable);
break;
@@ -752,7 +752,7 @@ namespace Barotrauma.Networking
}
IWriteMessage readyToStartMsg = new WriteOnlyMessage();
readyToStartMsg.Write((byte)ClientPacketHeader.RESPONSE_STARTGAME);
readyToStartMsg.WriteByte((byte)ClientPacketHeader.RESPONSE_STARTGAME);
if (campaign != null) { campaign.PendingSubmarineSwitch = null; }
GameMain.NetLobbyScreen.UsingShuttle = usingShuttle;
@@ -770,7 +770,7 @@ namespace Barotrauma.Networking
campaign.LastSaveID == campaignSaveID &&
campaignUpdateIDs.All(kvp => campaign.GetLastUpdateIdForFlag(kvp.Key) == kvp.Value);
}
readyToStartMsg.Write(readyToStart);
readyToStartMsg.WriteBoolean(readyToStart);
DebugConsole.Log(readyToStart ? "Ready to start." : "Not ready to start.");
@@ -1202,7 +1202,8 @@ namespace Barotrauma.Networking
VoipClient = new VoipClient(this, clientPeer);
if (Screen.Selected != GameMain.GameScreen && !(Screen.Selected is RoundSummaryScreen))
//if we're still in the game, roundsummary or lobby screen, we don't need to redownload the mods
if (!(Screen.Selected is GameScreen) && !(Screen.Selected is RoundSummaryScreen) && !(Screen.Selected is NetLobbyScreen))
{
GameMain.ModDownloadScreen.Select();
}
@@ -1639,7 +1640,7 @@ namespace Barotrauma.Networking
DateTime requestFinalizeTime = DateTime.Now;
TimeSpan requestFinalizeInterval = new TimeSpan(0, 0, 2);
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.REQUEST_STARTGAMEFINALIZE);
msg.WriteByte((byte)ClientPacketHeader.REQUEST_STARTGAMEFINALIZE);
clientPeer.Send(msg, DeliveryMethod.Unreliable);
GUIMessageBox interruptPrompt = null;
@@ -1653,7 +1654,7 @@ namespace Barotrauma.Networking
if (DateTime.Now > requestFinalizeTime)
{
msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.REQUEST_STARTGAMEFINALIZE);
msg.WriteByte((byte)ClientPacketHeader.REQUEST_STARTGAMEFINALIZE);
clientPeer.Send(msg, DeliveryMethod.Unreliable);
requestFinalizeTime = DateTime.Now + requestFinalizeInterval;
}
@@ -1816,16 +1817,24 @@ namespace Barotrauma.Networking
if (Screen.Selected == GameMain.GameScreen)
{
Submarine refSub = Submarine.MainSub;
if (Submarine.MainSubs[1] != null &&
GameMain.GameSession.GameMode is PvPMode &&
GameMain.GameSession.WinningTeam.HasValue && GameMain.GameSession.WinningTeam == CharacterTeamType.Team1)
{
refSub = Submarine.MainSubs[1];
}
// Enable characters near the main sub for the endCinematic
foreach (Character c in Character.CharacterList)
{
if (Vector2.DistanceSquared(Submarine.MainSub.WorldPosition, c.WorldPosition) < MathUtils.Pow2(c.Params.DisableDistance))
if (Vector2.DistanceSquared(refSub.WorldPosition, c.WorldPosition) < MathUtils.Pow2(c.Params.DisableDistance))
{
c.Enabled = true;
}
}
EndCinematic = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, Alignment.CenterLeft, Alignment.CenterRight);
EndCinematic = new CameraTransition(refSub, GameMain.GameScreen.Cam, Alignment.CenterLeft, Alignment.CenterRight);
while (EndCinematic.Running && Screen.Selected == GameMain.GameScreen)
{
yield return CoroutineStatus.Running;
@@ -1874,7 +1883,7 @@ namespace Barotrauma.Networking
}
GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.SubList, ServerSubmarines);
GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.ShuttleList.ListBox, ServerSubmarines);
GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.ShuttleList.ListBox, ServerSubmarines.Where(s => s.HasTag(SubmarineTag.Shuttle)));
gameStarted = inc.ReadBoolean();
bool allowSpectating = inc.ReadBoolean();
@@ -2073,7 +2082,7 @@ namespace Barotrauma.Networking
(isInitialUpdate || initialUpdateReceived))
{
ReadWriteMessage settingsBuf = new ReadWriteMessage();
settingsBuf.Write(settingsData, 0, settingsLen); settingsBuf.BitPosition = 0;
settingsBuf.WriteBytes(settingsData, 0, settingsLen); settingsBuf.BitPosition = 0;
serverSettings.ClientRead(settingsBuf);
if (!IsServerOwner)
{
@@ -2325,38 +2334,38 @@ namespace Barotrauma.Networking
private void SendLobbyUpdate()
{
IWriteMessage outmsg = new WriteOnlyMessage();
outmsg.Write((byte)ClientPacketHeader.UPDATE_LOBBY);
outmsg.WriteByte((byte)ClientPacketHeader.UPDATE_LOBBY);
outmsg.Write((byte)ClientNetObject.SYNC_IDS);
outmsg.Write(GameMain.NetLobbyScreen.LastUpdateID);
outmsg.Write(ChatMessage.LastID);
outmsg.Write(LastClientListUpdateID);
outmsg.Write(nameId);
outmsg.Write(Name);
outmsg.WriteByte((byte)ClientNetObject.SYNC_IDS);
outmsg.WriteUInt16(GameMain.NetLobbyScreen.LastUpdateID);
outmsg.WriteUInt16(ChatMessage.LastID);
outmsg.WriteUInt16(LastClientListUpdateID);
outmsg.WriteUInt16(nameId);
outmsg.WriteString(Name);
var jobPreferences = GameMain.NetLobbyScreen.JobPreferences;
if (jobPreferences.Count > 0)
{
outmsg.Write(jobPreferences[0].Prefab.Identifier);
outmsg.WriteIdentifier(jobPreferences[0].Prefab.Identifier);
}
else
{
outmsg.Write("");
outmsg.WriteIdentifier(Identifier.Empty);
}
outmsg.Write((byte)MultiplayerPreferences.Instance.TeamPreference);
outmsg.WriteByte((byte)MultiplayerPreferences.Instance.TeamPreference);
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.LastSaveID == 0)
{
outmsg.Write((UInt16)0);
outmsg.WriteUInt16((UInt16)0);
}
else
{
outmsg.Write(campaign.LastSaveID);
outmsg.Write(campaign.CampaignID);
outmsg.WriteUInt16(campaign.LastSaveID);
outmsg.WriteByte(campaign.CampaignID);
foreach (MultiPlayerCampaign.NetFlags netFlag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags)))
{
outmsg.Write(campaign.GetLastUpdateIdForFlag(netFlag));
outmsg.WriteUInt16(campaign.GetLastUpdateIdForFlag(netFlag));
}
outmsg.Write(GameMain.NetLobbyScreen.CampaignCharacterDiscarded);
outmsg.WriteBoolean(GameMain.NetLobbyScreen.CampaignCharacterDiscarded);
}
chatMsgQueue.RemoveAll(cMsg => !NetIdUtils.IdMoreRecent(cMsg.NetStateID, lastSentChatMsgID));
@@ -2369,7 +2378,7 @@ namespace Barotrauma.Networking
}
chatMsgQueue[i].ClientWrite(outmsg);
}
outmsg.Write((byte)ClientNetObject.END_OF_MESSAGE);
outmsg.WriteByte((byte)ClientNetObject.END_OF_MESSAGE);
if (outmsg.LengthBytes > MsgConstants.MTU)
{
@@ -2382,29 +2391,29 @@ namespace Barotrauma.Networking
private void SendIngameUpdate()
{
IWriteMessage outmsg = new WriteOnlyMessage();
outmsg.Write((byte)ClientPacketHeader.UPDATE_INGAME);
outmsg.Write(entityEventManager.MidRoundSyncingDone);
outmsg.WriteByte((byte)ClientPacketHeader.UPDATE_INGAME);
outmsg.WriteBoolean(entityEventManager.MidRoundSyncingDone);
outmsg.WritePadBits();
outmsg.Write((byte)ClientNetObject.SYNC_IDS);
outmsg.WriteByte((byte)ClientNetObject.SYNC_IDS);
//outmsg.Write(GameMain.NetLobbyScreen.LastUpdateID);
outmsg.Write(ChatMessage.LastID);
outmsg.Write(entityEventManager.LastReceivedID);
outmsg.Write(LastClientListUpdateID);
outmsg.WriteUInt16(ChatMessage.LastID);
outmsg.WriteUInt16(entityEventManager.LastReceivedID);
outmsg.WriteUInt16(LastClientListUpdateID);
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.LastSaveID == 0)
{
outmsg.Write((UInt16)0);
outmsg.WriteUInt16((UInt16)0);
}
else
{
outmsg.Write(campaign.LastSaveID);
outmsg.Write(campaign.CampaignID);
outmsg.WriteUInt16(campaign.LastSaveID);
outmsg.WriteByte(campaign.CampaignID);
foreach (MultiPlayerCampaign.NetFlags flag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags)))
{
outmsg.Write(campaign.GetLastUpdateIdForFlag(flag));
outmsg.WriteUInt16(campaign.GetLastUpdateIdForFlag(flag));
}
outmsg.Write(GameMain.NetLobbyScreen.CampaignCharacterDiscarded);
outmsg.WriteBoolean(GameMain.NetLobbyScreen.CampaignCharacterDiscarded);
}
Character.Controlled?.ClientWriteInput(outmsg);
@@ -2423,7 +2432,7 @@ namespace Barotrauma.Networking
chatMsgQueue[i].ClientWrite(outmsg);
}
outmsg.Write((byte)ClientNetObject.END_OF_MESSAGE);
outmsg.WriteByte((byte)ClientNetObject.END_OF_MESSAGE);
if (outmsg.LengthBytes > MsgConstants.MTU)
{
@@ -2462,8 +2471,8 @@ namespace Barotrauma.Networking
{
WaitForNextRoundRespawn = waitForNextRoundRespawn;
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.READY_TO_SPAWN);
msg.Write((bool)waitForNextRoundRespawn);
msg.WriteByte((byte)ClientPacketHeader.READY_TO_SPAWN);
msg.WriteBoolean((bool)waitForNextRoundRespawn);
clientPeer?.Send(msg, DeliveryMethod.Reliable);
}
@@ -2475,13 +2484,13 @@ namespace Barotrauma.Networking
$"Sending a file request to the server (type: {fileType}, path: {file ?? "null"}");
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.FILE_REQUEST);
msg.Write((byte)FileTransferMessageType.Initiate);
msg.Write((byte)fileType);
msg.WriteByte((byte)ClientPacketHeader.FILE_REQUEST);
msg.WriteByte((byte)FileTransferMessageType.Initiate);
msg.WriteByte((byte)fileType);
if (fileType != FileTransferType.CampaignSave)
{
msg.Write(file ?? throw new ArgumentNullException(nameof(file)));
msg.Write(fileHash ?? throw new ArgumentNullException(nameof(fileHash)));
msg.WriteString(file ?? throw new ArgumentNullException(nameof(file)));
msg.WriteString(fileHash ?? throw new ArgumentNullException(nameof(fileHash)));
}
clientPeer.Send(msg, DeliveryMethod.Reliable);
}
@@ -2500,20 +2509,20 @@ namespace Barotrauma.Networking
transfer.RecordOffsetAckTime();
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.FILE_REQUEST);
msg.Write((byte)FileTransferMessageType.Data);
msg.Write((byte)transfer.ID);
msg.Write(expecting);
msg.Write(lastSeen);
msg.WriteByte((byte)ClientPacketHeader.FILE_REQUEST);
msg.WriteByte((byte)FileTransferMessageType.Data);
msg.WriteByte((byte)transfer.ID);
msg.WriteInt32(expecting);
msg.WriteInt32(lastSeen);
clientPeer.Send(msg, reliable ? DeliveryMethod.Reliable : DeliveryMethod.Unreliable);
}
public void CancelFileTransfer(int id)
{
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.FILE_REQUEST);
msg.Write((byte)FileTransferMessageType.Cancel);
msg.Write((byte)id);
msg.WriteByte((byte)ClientPacketHeader.FILE_REQUEST);
msg.WriteByte((byte)FileTransferMessageType.Cancel);
msg.WriteByte((byte)id);
clientPeer.Send(msg, DeliveryMethod.Reliable);
}
@@ -2547,8 +2556,7 @@ namespace Barotrauma.Networking
Color newSubTextColor = new Color(subElement.GetChild<GUITextBlock>().TextColor, 1.0f);
subElement.GetChild<GUITextBlock>().TextColor = newSubTextColor;
GUITextBlock classTextBlock = subElement.GetChildByUserData("classtext") as GUITextBlock;
if (classTextBlock != null)
if (subElement.GetChildByUserData("classtext") is GUITextBlock classTextBlock)
{
Color newSubClassTextColor = new Color(classTextBlock.TextColor, 0.8f);
classTextBlock.Text = TextManager.Get($"submarineclass.{newSub.SubmarineClass}");
@@ -2742,39 +2750,39 @@ namespace Barotrauma.Networking
public void SendCharacterInfo(string newName = null)
{
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.UPDATE_CHARACTERINFO);
msg.WriteByte((byte)ClientPacketHeader.UPDATE_CHARACTERINFO);
WriteCharacterInfo(msg, newName);
msg.Write((byte)ServerNetObject.END_OF_MESSAGE);
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
clientPeer?.Send(msg, DeliveryMethod.Reliable);
}
public void WriteCharacterInfo(IWriteMessage msg, string newName = null)
{
msg.Write(characterInfo == null);
msg.WriteBoolean(characterInfo == null);
if (characterInfo == null) { return; }
msg.Write(newName ?? string.Empty);
msg.WriteString(newName ?? string.Empty);
msg.Write((byte)characterInfo.Head.Preset.TagSet.Count);
msg.WriteByte((byte)characterInfo.Head.Preset.TagSet.Count);
foreach (Identifier tag in characterInfo.Head.Preset.TagSet)
{
msg.Write(tag);
msg.WriteIdentifier(tag);
}
msg.Write((byte)characterInfo.Head.HairIndex);
msg.Write((byte)characterInfo.Head.BeardIndex);
msg.Write((byte)characterInfo.Head.MoustacheIndex);
msg.Write((byte)characterInfo.Head.FaceAttachmentIndex);
msg.WriteByte((byte)characterInfo.Head.HairIndex);
msg.WriteByte((byte)characterInfo.Head.BeardIndex);
msg.WriteByte((byte)characterInfo.Head.MoustacheIndex);
msg.WriteByte((byte)characterInfo.Head.FaceAttachmentIndex);
msg.WriteColorR8G8B8(characterInfo.Head.SkinColor);
msg.WriteColorR8G8B8(characterInfo.Head.HairColor);
msg.WriteColorR8G8B8(characterInfo.Head.FacialHairColor);
var jobPreferences = GameMain.NetLobbyScreen.JobPreferences;
int count = Math.Min(jobPreferences.Count, 3);
msg.Write((byte)count);
msg.WriteByte((byte)count);
for (int i = 0; i < count; i++)
{
msg.Write(jobPreferences[i].Prefab.Identifier);
msg.Write((byte)jobPreferences[i].Variant);
msg.WriteIdentifier(jobPreferences[i].Prefab.Identifier);
msg.WriteByte((byte)jobPreferences[i].Variant);
}
}
@@ -2783,10 +2791,10 @@ namespace Barotrauma.Networking
if (clientPeer == null) { return; }
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.UPDATE_LOBBY);
msg.Write((byte)ClientNetObject.VOTE);
msg.WriteByte((byte)ClientPacketHeader.UPDATE_LOBBY);
msg.WriteByte((byte)ClientNetObject.VOTE);
Voting.ClientWrite(msg, voteType, data);
msg.Write((byte)ServerNetObject.END_OF_MESSAGE);
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
clientPeer.Send(msg, DeliveryMethod.Reliable);
}
@@ -2794,7 +2802,6 @@ namespace Barotrauma.Networking
public void VoteForKick(Client votedClient)
{
if (votedClient == null) { return; }
votedClient.AddKickVote(ConnectedClients.FirstOrDefault(c => c.SessionId == SessionId));
Vote(VoteType.Kick, votedClient);
}
@@ -2840,10 +2847,10 @@ namespace Barotrauma.Networking
public override void KickPlayer(string kickedName, string reason)
{
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
msg.Write((UInt16)ClientPermissions.Kick);
msg.Write(kickedName);
msg.Write(reason);
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
msg.WriteUInt16((UInt16)ClientPermissions.Kick);
msg.WriteString(kickedName);
msg.WriteString(reason);
clientPeer.Send(msg, DeliveryMethod.Reliable);
}
@@ -2851,11 +2858,11 @@ namespace Barotrauma.Networking
public override void BanPlayer(string kickedName, string reason, TimeSpan? duration = null)
{
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
msg.Write((UInt16)ClientPermissions.Ban);
msg.Write(kickedName);
msg.Write(reason);
msg.Write(duration.HasValue ? duration.Value.TotalSeconds : 0.0); //0 = permaban
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
msg.WriteUInt16((UInt16)ClientPermissions.Ban);
msg.WriteString(kickedName);
msg.WriteString(reason);
msg.WriteDouble(duration.HasValue ? duration.Value.TotalSeconds : 0.0); //0 = permaban
clientPeer.Send(msg, DeliveryMethod.Reliable);
}
@@ -2863,28 +2870,28 @@ namespace Barotrauma.Networking
public override void UnbanPlayer(string playerName)
{
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
msg.Write((UInt16)ClientPermissions.Unban);
msg.Write(true); msg.WritePadBits();
msg.Write(playerName);
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
msg.WriteUInt16((UInt16)ClientPermissions.Unban);
msg.WriteBoolean(true); msg.WritePadBits();
msg.WriteString(playerName);
clientPeer.Send(msg, DeliveryMethod.Reliable);
}
public override void UnbanPlayer(Endpoint endpoint)
{
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
msg.Write((UInt16)ClientPermissions.Unban);
msg.Write(false); msg.WritePadBits();
msg.Write(endpoint.StringRepresentation);
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
msg.WriteUInt16((UInt16)ClientPermissions.Unban);
msg.WriteBoolean(false); msg.WritePadBits();
msg.WriteString(endpoint.StringRepresentation);
clientPeer.Send(msg, DeliveryMethod.Reliable);
}
public void UpdateClientPermissions(Client targetClient)
{
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
msg.Write((UInt16)ClientPermissions.ManagePermissions);
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
msg.WriteUInt16((UInt16)ClientPermissions.ManagePermissions);
targetClient.WritePermissions(msg);
clientPeer.Send(msg, DeliveryMethod.Reliable);
}
@@ -2897,10 +2904,10 @@ namespace Barotrauma.Networking
return;
}
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
msg.Write((UInt16)ClientPermissions.ManageCampaign);
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
msg.WriteUInt16((UInt16)ClientPermissions.ManageCampaign);
campaign.ClientWrite(msg);
msg.Write((byte)ServerNetObject.END_OF_MESSAGE);
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
clientPeer.Send(msg, DeliveryMethod.Reliable);
}
@@ -2913,12 +2920,12 @@ namespace Barotrauma.Networking
}
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
msg.Write((UInt16)ClientPermissions.ConsoleCommands);
msg.Write(command);
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
msg.WriteUInt16((UInt16)ClientPermissions.ConsoleCommands);
msg.WriteString(command);
Vector2 cursorWorldPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition);
msg.Write(cursorWorldPos.X);
msg.Write(cursorWorldPos.Y);
msg.WriteSingle(cursorWorldPos.X);
msg.WriteSingle(cursorWorldPos.Y);
clientPeer.Send(msg, DeliveryMethod.Reliable);
}
@@ -2929,10 +2936,10 @@ namespace Barotrauma.Networking
public void RequestStartRound(bool continueCampaign = false)
{
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
msg.Write((UInt16)ClientPermissions.ManageRound);
msg.Write(false); //indicates round start
msg.Write(continueCampaign);
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
msg.WriteUInt16((UInt16)ClientPermissions.ManageRound);
msg.WriteBoolean(false); //indicates round start
msg.WriteBoolean(continueCampaign);
clientPeer.Send(msg, DeliveryMethod.Reliable);
}
@@ -2940,25 +2947,16 @@ namespace Barotrauma.Networking
/// <summary>
/// Tell the server to select a submarine (permission required)
/// </summary>
public void RequestSelectSub(int subIndex, bool isShuttle)
public void RequestSelectSub(SubmarineInfo sub, bool isShuttle)
{
if (!HasPermission(ClientPermissions.SelectSub)) return;
var subList = isShuttle ? GameMain.NetLobbyScreen.ShuttleList.ListBox : GameMain.NetLobbyScreen.SubList;
if (subIndex < 0 || subIndex >= subList.Content.CountChildren)
{
DebugConsole.ThrowError("Submarine index out of bounds (" + subIndex + ")\n" + Environment.StackTrace.CleanupStackTrace());
return;
}
if (!HasPermission(ClientPermissions.SelectSub) || sub == null) { return; }
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
msg.Write((UInt16)ClientPermissions.SelectSub);
msg.Write(isShuttle); msg.WritePadBits();
msg.Write((UInt16)subIndex);
msg.Write((byte)ServerNetObject.END_OF_MESSAGE);
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
msg.WriteUInt16((UInt16)ClientPermissions.SelectSub);
msg.WriteBoolean(isShuttle); msg.WritePadBits();
msg.WriteString(sub.MD5Hash.StringRepresentation);
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
clientPeer.Send(msg, DeliveryMethod.Reliable);
}
@@ -2975,10 +2973,10 @@ namespace Barotrauma.Networking
}
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
msg.Write((UInt16)ClientPermissions.SelectMode);
msg.Write((UInt16)modeIndex);
msg.Write((byte)ServerNetObject.END_OF_MESSAGE);
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
msg.WriteUInt16((UInt16)ClientPermissions.SelectMode);
msg.WriteUInt16((UInt16)modeIndex);
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
clientPeer.Send(msg, DeliveryMethod.Reliable);
}
@@ -2991,14 +2989,14 @@ namespace Barotrauma.Networking
saveName = Path.GetFileNameWithoutExtension(saveName);
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.CAMPAIGN_SETUP_INFO);
msg.WriteByte((byte)ClientPacketHeader.CAMPAIGN_SETUP_INFO);
msg.Write(true); msg.WritePadBits();
msg.Write(saveName);
msg.Write(mapSeed);
msg.Write(sub.Name);
msg.Write(sub.MD5Hash.StringRepresentation);
msg.Write(settings);
msg.WriteBoolean(true); msg.WritePadBits();
msg.WriteString(saveName);
msg.WriteString(mapSeed);
msg.WriteString(sub.Name);
msg.WriteString(sub.MD5Hash.StringRepresentation);
msg.WriteNetSerializableStruct(settings);
clientPeer.Send(msg, DeliveryMethod.Reliable);
}
@@ -3011,10 +3009,10 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen.CampaignFrame.Visible = false;
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.CAMPAIGN_SETUP_INFO);
msg.WriteByte((byte)ClientPacketHeader.CAMPAIGN_SETUP_INFO);
msg.Write(false); msg.WritePadBits();
msg.Write(saveName);
msg.WriteBoolean(false); msg.WritePadBits();
msg.WriteString(saveName);
clientPeer.Send(msg, DeliveryMethod.Reliable);
}
@@ -3025,10 +3023,10 @@ namespace Barotrauma.Networking
public void RequestRoundEnd(bool save)
{
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
msg.Write((UInt16)ClientPermissions.ManageRound);
msg.Write(true); //indicates round end
msg.Write(save);
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
msg.WriteUInt16((UInt16)ClientPermissions.ManageRound);
msg.WriteBoolean(true); //indicates round end
msg.WriteBoolean(save);
clientPeer.Send(msg, DeliveryMethod.Reliable);
}
@@ -3049,11 +3047,11 @@ namespace Barotrauma.Networking
if (clientPeer == null) { return false; }
IWriteMessage readyToStartMsg = new WriteOnlyMessage();
readyToStartMsg.Write((byte)ClientPacketHeader.RESPONSE_STARTGAME);
readyToStartMsg.WriteByte((byte)ClientPacketHeader.RESPONSE_STARTGAME);
//assume we have the required sub files to start the round
//(if not, we'll find out when the server sends the STARTGAME message and can initiate a file transfer)
readyToStartMsg.Write(true);
readyToStartMsg.WriteBoolean(true);
WriteCharacterInfo(readyToStartMsg);
@@ -3480,10 +3478,6 @@ namespace Barotrauma.Networking
UserData = client,
OnClicked = (btn, userdata) => { VoteForKick(client); btn.Enabled = false; return true; }
};
if (GameMain.NetworkMember.ConnectedClients != null)
{
kickVoteButton.Enabled = !client.HasKickVoteFromSessionId(SessionId);
}
}
}
@@ -3568,21 +3562,21 @@ namespace Barotrauma.Networking
public void ReportError(ClientNetError error, UInt16 expectedId = 0, UInt16 eventId = 0, UInt16 entityId = 0)
{
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write((byte)ClientPacketHeader.ERROR);
outMsg.Write((byte)error);
outMsg.WriteByte((byte)ClientPacketHeader.ERROR);
outMsg.WriteByte((byte)error);
switch (error)
{
case ClientNetError.MISSING_EVENT:
outMsg.Write(expectedId);
outMsg.Write(eventId);
outMsg.WriteUInt16(expectedId);
outMsg.WriteUInt16(eventId);
break;
case ClientNetError.MISSING_ENTITY:
outMsg.Write(eventId);
outMsg.Write(entityId);
outMsg.Write((byte)Submarine.Loaded.Count);
outMsg.WriteUInt16(eventId);
outMsg.WriteUInt16(entityId);
outMsg.WriteByte((byte)Submarine.Loaded.Count);
foreach (Submarine sub in Submarine.Loaded)
{
outMsg.Write(sub.Info.Name);
outMsg.WriteString(sub.Info.Name);
}
break;
}
@@ -103,7 +103,7 @@ namespace Barotrauma.Networking
eventLastSent[entityEvent.ID] = (float)Lidgren.Network.NetTime.Now;
}
msg.Write((byte)ClientNetObject.ENTITY_STATE);
msg.WriteByte((byte)ClientNetObject.ENTITY_STATE);
Write(msg, eventsToSync, out _);
}
@@ -17,7 +17,7 @@ namespace Barotrauma.Networking
public void Write(IWriteMessage msg)
{
msg.Write(CharacterStateID);
msg.WriteUInt16(CharacterStateID);
serializable.ClientEventWrite(msg, Data);
}
}
@@ -6,8 +6,8 @@ namespace Barotrauma.Networking
{
public override void ClientWrite(IWriteMessage msg)
{
msg.Write((byte)ClientNetObject.CHAT_MESSAGE);
msg.Write(NetStateID);
msg.WriteByte((byte)ClientNetObject.CHAT_MESSAGE);
msg.WriteUInt16(NetStateID);
msg.WriteRangedInteger((int)ChatMessageType.Order, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1);
msg.WriteRangedInteger((int)ChatMode.None, 0, Enum.GetValues(typeof(ChatMode)).Length - 1);
WriteOrder(msg);
@@ -1,60 +1,23 @@
#nullable enable
using Barotrauma.Steam;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
namespace Barotrauma.Networking
{
abstract class ClientPeer
internal abstract class ClientPeer
{
public class ServerContentPackage
{
public readonly string Name;
public readonly Md5Hash Hash;
public readonly UInt64 WorkshopId;
public readonly DateTime InstallTime;
public RegularPackage? RegularPackage
{
get
{
return ContentPackageManager.RegularPackages.FirstOrDefault(p => p.Hash.Equals(Hash));
}
}
public CorePackage? CorePackage
{
get
{
return ContentPackageManager.CorePackages.FirstOrDefault(p => p.Hash.Equals(Hash));
}
}
public ContentPackage? ContentPackage
=> (ContentPackage?)RegularPackage ?? CorePackage;
public string GetPackageStr()
=> $"\"{Name}\" (hash {Hash.ShortRepresentation})";
public ServerContentPackage(string name, Md5Hash hash, UInt64 workshopId, DateTime installTime)
{
Name = name;
Hash = hash;
WorkshopId = workshopId;
InstallTime = installTime;
}
}
public ImmutableArray<ServerContentPackage> ServerContentPackages { get; set; } =
ImmutableArray<ServerContentPackage>.Empty;
public delegate void MessageCallback(IReadMessage message);
public delegate void DisconnectCallback(bool disableReconnect);
public delegate void DisconnectMessageCallback(string message);
public delegate void PasswordCallback(int salt, int retries);
public delegate void InitializationCompleteCallback();
[Obsolete("TODO: delete in nr3-layer-1-2-cleanup")]
@@ -65,8 +28,12 @@ namespace Barotrauma.Networking
public readonly DisconnectMessageCallback OnDisconnectMessageReceived;
public readonly PasswordCallback OnRequestPassword;
public readonly InitializationCompleteCallback OnInitializationComplete;
public Callbacks(MessageCallback onMessageReceived, DisconnectCallback onDisconnect, DisconnectMessageCallback onDisconnectMessageReceived, PasswordCallback onRequestPassword, InitializationCompleteCallback onInitializationComplete)
public Callbacks(MessageCallback onMessageReceived,
DisconnectCallback onDisconnect,
DisconnectMessageCallback onDisconnectMessageReceived,
PasswordCallback onRequestPassword,
InitializationCompleteCallback onInitializationComplete)
{
OnMessageReceived = onMessageReceived;
OnDisconnect = onDisconnect;
@@ -91,98 +58,106 @@ namespace Barotrauma.Networking
this.ownerKey = ownerKey;
isOwner = ownerKey.IsSome();
}
public abstract void Start();
public abstract void Close(string? msg = null, bool disableReconnect = false);
public abstract void Update(float deltaTime);
public abstract void Send(IWriteMessage msg, DeliveryMethod deliveryMethod, bool compressPastThreshold = true);
public abstract void SendPassword(string password);
protected abstract void SendMsgInternal(DeliveryMethod deliveryMethod, IWriteMessage msg);
protected abstract void SendMsgInternal(PeerPacketHeaders headers, INetSerializableStruct? body);
protected ConnectionInitialization initializationStep;
public bool ContentPackageOrderReceived { get; protected set; }
protected int passwordSalt;
protected Steamworks.AuthTicket? steamAuthTicket;
protected void ReadConnectionInitializationStep(IReadMessage inc)
public struct IncomingInitializationMessage
{
ConnectionInitialization step = (ConnectionInitialization)inc.ReadByte();
public ConnectionInitialization InitializationStep;
public IReadMessage Message;
}
IWriteMessage outMsg;
switch (step)
protected void ReadConnectionInitializationStep(IncomingInitializationMessage inc)
{
switch (inc.InitializationStep)
{
case ConnectionInitialization.SteamTicketAndVersion:
{
if (initializationStep != ConnectionInitialization.SteamTicketAndVersion) { return; }
outMsg = new WriteOnlyMessage();
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
outMsg.Write((byte)ConnectionInitialization.SteamTicketAndVersion);
outMsg.Write(GameMain.Client.Name);
outMsg.Write(ownerKey.Fallback(0));
outMsg.Write(SteamManager.GetSteamId().Select(steamId => steamId.Value).Fallback(0));
if (steamAuthTicket == null)
{
outMsg.Write((UInt16)0);
}
else
{
outMsg.Write((UInt16)steamAuthTicket.Data.Length);
outMsg.Write(steamAuthTicket.Data, 0, steamAuthTicket.Data.Length);
}
outMsg.Write(GameMain.Version.ToString());
outMsg.Write(GameSettings.CurrentConfig.Language.Value);
SendMsgInternal(DeliveryMethod.Reliable, outMsg);
PeerPacketHeaders headers = new PeerPacketHeaders
{
DeliveryMethod = DeliveryMethod.Reliable,
PacketHeader = PacketHeader.IsConnectionInitializationStep,
Initialization = ConnectionInitialization.SteamTicketAndVersion
};
ClientSteamTicketAndVersionPacket body = new ClientSteamTicketAndVersionPacket
{
Name = GameMain.Client.Name,
OwnerKey = ownerKey,
SteamId = SteamManager.GetSteamId().Select(id => (AccountId)id),
SteamAuthTicket = steamAuthTicket switch
{
null => Option<byte[]>.None(),
var ticket => Option<byte[]>.Some(ticket.Data)
},
GameVersion = GameMain.Version.ToString(),
Language = GameSettings.CurrentConfig.Language.Value
};
SendMsgInternal(headers, body);
break;
}
case ConnectionInitialization.ContentPackageOrder:
{
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion ||
initializationStep == ConnectionInitialization.Password) { initializationStep = ConnectionInitialization.ContentPackageOrder; }
if (initializationStep != ConnectionInitialization.ContentPackageOrder) { return; }
outMsg = new WriteOnlyMessage();
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
outMsg.Write((byte)ConnectionInitialization.ContentPackageOrder);
UInt32 packageCount = inc.ReadVariableUInt32();
List<ServerContentPackage> serverPackages = new List<ServerContentPackage>();
for (int i = 0; i < packageCount; i++)
initializationStep == ConnectionInitialization.Password)
{
string name = inc.ReadString();
UInt32 hashByteCount = inc.ReadVariableUInt32();
byte[] hashBytes = inc.ReadBytes((int)hashByteCount);
UInt64 workshopId = inc.ReadUInt64();
UInt32 installTimeDiffSeconds = inc.ReadUInt32();
DateTime installTime = DateTime.UtcNow + TimeSpan.FromSeconds(installTimeDiffSeconds);
var pkg = new ServerContentPackage(name, Md5Hash.BytesAsHash(hashBytes), workshopId, installTime);
serverPackages.Add(pkg);
initializationStep = ConnectionInitialization.ContentPackageOrder;
}
if (initializationStep != ConnectionInitialization.ContentPackageOrder) { return; }
PeerPacketHeaders headers = new PeerPacketHeaders
{
DeliveryMethod = DeliveryMethod.Reliable,
PacketHeader = PacketHeader.IsConnectionInitializationStep,
Initialization = ConnectionInitialization.ContentPackageOrder
};
var orderPacket = INetSerializableStruct.Read<ServerPeerContentPackageOrderPacket>(inc.Message);
if (!ContentPackageOrderReceived)
{
ServerContentPackages = serverPackages.ToImmutableArray();
if (serverPackages.Count == 0)
ServerContentPackages = orderPacket.ContentPackages;
if (ServerContentPackages.Length == 0)
{
string errorMsg = "Error in ContentPackageOrder message: list of content packages enabled on the server was empty.";
GameAnalyticsManager.AddErrorEventOnce("ClientPeer.ReadConnectionInitializationStep:NoContentPackages", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
DebugConsole.ThrowError(errorMsg);
}
ContentPackageOrderReceived = true;
SendMsgInternal(DeliveryMethod.Reliable, outMsg);
SendMsgInternal(headers, null);
}
break;
}
case ConnectionInitialization.Password:
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion) { initializationStep = ConnectionInitialization.Password; }
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion)
{
initializationStep = ConnectionInitialization.Password;
}
if (initializationStep != ConnectionInitialization.Password) { return; }
bool incomingSalt = inc.ReadBoolean(); inc.ReadPadBits();
int retries = 0;
if (incomingSalt)
{
passwordSalt = inc.ReadInt32();
}
else
{
retries = inc.ReadInt32();
}
var passwordPacket = INetSerializableStruct.Read<ServerPeerPasswordPacket>(inc.Message);
passwordPacket.Salt.TryUnwrap(out passwordSalt);
passwordPacket.RetriesLeft.TryUnwrap(out var retries);
callbacks.OnRequestPassword.Invoke(passwordSalt, retries);
break;
}
@@ -192,4 +167,4 @@ namespace Barotrauma.Networking
public abstract void ForceTimeOut();
#endif
}
}
}
@@ -1,21 +1,22 @@
using Barotrauma.Steam;
using Lidgren.Network;
#nullable enable
using System;
using System.Collections.Generic;
using System.Text;
using Lidgren.Network;
using Barotrauma.Steam;
namespace Barotrauma.Networking
{
class LidgrenClientPeer : ClientPeer
internal sealed class LidgrenClientPeer : ClientPeer
{
private bool isActive;
private NetClient netClient;
private NetPeerConfiguration netPeerConfiguration;
private NetClient? netClient;
private readonly NetPeerConfiguration netPeerConfiguration;
List<NetIncomingMessage> incomingLidgrenMessages;
private readonly List<NetIncomingMessage> incomingLidgrenMessages;
private LidgrenEndpoint lidgrenEndpoint
=> ServerConnection is LidgrenConnection { Endpoint: LidgrenEndpoint result }
private LidgrenEndpoint lidgrenEndpoint =>
ServerConnection is LidgrenConnection { Endpoint: LidgrenEndpoint result }
? result
: throw new InvalidOperationException();
@@ -25,13 +26,6 @@ namespace Barotrauma.Networking
netClient = null;
isActive = false;
}
public override void Start()
{
if (isActive) { return; }
ContentPackageOrderReceived = false;
netPeerConfiguration = new NetPeerConfiguration("barotrauma")
{
@@ -45,6 +39,17 @@ namespace Barotrauma.Networking
| NetIncomingMessageType.ErrorMessage
| NetIncomingMessageType.Error);
incomingLidgrenMessages = new List<NetIncomingMessage>();
}
public override void Start()
{
if (isActive) { return; }
incomingLidgrenMessages.Clear();
ContentPackageOrderReceived = false;
netClient = new NetClient(netPeerConfiguration);
if (SteamManager.IsInitialized)
@@ -56,14 +61,13 @@ namespace Barotrauma.Networking
}
}
incomingLidgrenMessages = new List<NetIncomingMessage>();
initializationStep = ConnectionInitialization.SteamTicketAndVersion;
if (!(ServerEndpoint is LidgrenEndpoint lidgrenEndpoint))
if (!(ServerEndpoint is LidgrenEndpoint lidgrenEndpointValue))
{
throw new InvalidCastException("endPoint is not IPEndPoint");
throw new InvalidCastException($"Endpoint is not {nameof(LidgrenEndpoint)}");
}
if (ServerConnection != null)
{
throw new InvalidOperationException("ServerConnection is not null");
@@ -71,8 +75,8 @@ namespace Barotrauma.Networking
netClient.Start();
var netConnection = netClient.Connect(lidgrenEndpoint.NetEndpoint);
var netConnection = netClient.Connect(lidgrenEndpointValue.NetEndpoint);
ServerConnection = new LidgrenConnection(netConnection)
{
Status = NetworkConnectionStatus.Connected
@@ -85,11 +89,18 @@ namespace Barotrauma.Networking
{
if (!isActive) { return; }
ToolBox.ThrowIfNull(netClient);
ToolBox.ThrowIfNull(incomingLidgrenMessages);
if (isOwner && !(ChildServerRelay.Process is { HasExited: false }))
{
Close();
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage);
msgBox.Buttons[0].OnClicked += (btn, obj) => { GameMain.MainMenuScreen.Select(); return false; };
msgBox.Buttons[0].OnClicked += (btn, obj) =>
{
GameMain.MainMenuScreen.Select();
return false;
};
return;
}
@@ -101,7 +112,11 @@ namespace Barotrauma.Networking
foreach (NetIncomingMessage inc in incomingLidgrenMessages)
{
if (!inc.SenderConnection.RemoteEndPoint.Equals(lidgrenEndpoint.NetEndpoint)) { continue; }
if (!inc.SenderConnection.RemoteEndPoint.Equals(lidgrenEndpoint.NetEndpoint))
{
DebugConsole.AddWarning($"Mismatched endpoint: expected {lidgrenEndpoint.NetEndpoint}, got {inc.SenderConnection.RemoteEndPoint}");
continue;
}
switch (inc.MessageType)
{
@@ -115,15 +130,23 @@ namespace Barotrauma.Networking
}
}
private void HandleDataMessage(NetIncomingMessage inc)
private void HandleDataMessage(NetIncomingMessage lidgrenMsg)
{
if (!isActive) { return; }
PacketHeader packetHeader = (PacketHeader)inc.ReadByte();
ToolBox.ThrowIfNull(ServerConnection);
IReadMessage inc = lidgrenMsg.ToReadMessage();
var (_, packetHeader, initialization) = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
if (packetHeader.IsConnectionInitializationStep() && initializationStep != ConnectionInitialization.Success)
{
ReadConnectionInitializationStep(new ReadWriteMessage(inc.Data, (int)inc.Position, inc.LengthBits, false));
ReadConnectionInitializationStep(new IncomingInitializationMessage
{
InitializationStep = initialization ?? throw new Exception("Initialization step missing"),
Message = inc
});
}
else
{
@@ -132,9 +155,9 @@ namespace Barotrauma.Networking
callbacks.OnInitializationComplete.Invoke();
initializationStep = ConnectionInitialization.Success;
}
UInt16 length = inc.ReadUInt16();
IReadMessage msg = new ReadOnlyMessage(inc.Data, packetHeader.IsCompressed(), inc.PositionInBytes, length, ServerConnection);
callbacks.OnMessageReceived.Invoke(msg);
var packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
callbacks.OnMessageReceived.Invoke(packet.GetReadMessage(packetHeader.IsCompressed(), ServerConnection));
}
}
@@ -142,7 +165,7 @@ namespace Barotrauma.Networking
{
if (!isActive) { return; }
NetConnectionStatus status = (NetConnectionStatus)inc.ReadByte();
NetConnectionStatus status = inc.ReadHeader<NetConnectionStatus>();
switch (status)
{
case NetConnectionStatus.Disconnected:
@@ -157,29 +180,38 @@ namespace Barotrauma.Networking
{
if (!isActive) { return; }
ToolBox.ThrowIfNull(netClient);
if (initializationStep != ConnectionInitialization.Password) { return; }
NetOutgoingMessage outMsg = netClient.CreateMessage();
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
outMsg.Write((byte)ConnectionInitialization.Password);
byte[] saltedPw = ServerSettings.SaltPassword(Encoding.UTF8.GetBytes(password), passwordSalt);
outMsg.Write((byte)saltedPw.Length);
outMsg.Write(saltedPw, 0, saltedPw.Length);
NetSendResult result = netClient.SendMessage(outMsg, NetDeliveryMethod.ReliableUnordered);
if (result != NetSendResult.Queued && result != NetSendResult.Sent)
var headers = new PeerPacketHeaders
{
DebugConsole.NewMessage("Failed to send " + initializationStep.ToString() + " message to host: " + result);
}
DeliveryMethod = DeliveryMethod.Reliable,
PacketHeader = PacketHeader.IsConnectionInitializationStep,
Initialization = ConnectionInitialization.Password
};
var body = new ClientPeerPasswordPacket
{
Password = ServerSettings.SaltPassword(Encoding.UTF8.GetBytes(password), passwordSalt)
};
SendMsgInternal(headers, body);
}
public override void Close(string msg = null, bool disableReconnect = false)
public override void Close(string? msg = null, bool disableReconnect = false)
{
if (!isActive) { return; }
ToolBox.ThrowIfNull(netClient);
isActive = false;
netClient.Shutdown(msg ?? TextManager.Get("Disconnecting").Value);
netClient = null;
steamAuthTicket?.Cancel(); steamAuthTicket = null;
steamAuthTicket?.Cancel();
steamAuthTicket = null;
callbacks.OnDisconnect.Invoke(disableReconnect);
}
@@ -187,19 +219,8 @@ namespace Barotrauma.Networking
{
if (!isActive) { 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;
}
ToolBox.ThrowIfNull(netClient);
ToolBox.ThrowIfNull(netPeerConfiguration);
#if DEBUG
netPeerConfiguration.SimulatedDuplicatesChance = GameMain.Client.SimulatedDuplicatesChance;
@@ -208,30 +229,42 @@ namespace Barotrauma.Networking
netPeerConfiguration.SimulatedLoss = GameMain.Client.SimulatedLoss;
#endif
NetOutgoingMessage lidgrenMsg = netClient.CreateMessage();
byte[] msgData = new byte[msg.LengthBytes];
msg.PrepareForSending(ref msgData, compressPastThreshold, out bool isCompressed, out int length);
lidgrenMsg.Write((byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None));
lidgrenMsg.Write((UInt16)length);
lidgrenMsg.Write(msgData, 0, length);
byte[] bufAux = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _);
NetSendResult result = netClient.SendMessage(lidgrenMsg, lidgrenDeliveryMethod);
var headers = new PeerPacketHeaders
{
DeliveryMethod = deliveryMethod,
PacketHeader = isCompressed ? PacketHeader.IsCompressed : PacketHeader.None,
Initialization = null
};
var body = new PeerPacketMessage
{
Buffer = bufAux
};
SendMsgInternal(headers, body);
}
protected override void SendMsgInternal(PeerPacketHeaders headers, INetSerializableStruct? body)
{
ToolBox.ThrowIfNull(netClient);
IWriteMessage msg = new WriteOnlyMessage();
msg.WriteNetSerializableStruct(headers);
body?.Write(msg);
NetSendResult result = ForwardToLidgren(msg, DeliveryMethod.Reliable);
if (result != NetSendResult.Queued && result != NetSendResult.Sent)
{
DebugConsole.NewMessage("Failed to send message to host: " + result);
DebugConsole.NewMessage($"Failed to send message to host: {result}\n{Environment.StackTrace}");
}
}
protected override void SendMsgInternal(DeliveryMethod deliveryMethod, IWriteMessage msg)
private NetSendResult ForwardToLidgren(IWriteMessage msg, DeliveryMethod deliveryMethod)
{
NetOutgoingMessage lidgrenMsg = netClient.CreateMessage();
lidgrenMsg.Write(msg.Buffer, 0, msg.LengthBytes);
ToolBox.ThrowIfNull(netClient);
NetSendResult result = netClient.SendMessage(lidgrenMsg, NetDeliveryMethod.ReliableUnordered);
if (result != NetSendResult.Queued && result != NetSendResult.Sent)
{
DebugConsole.NewMessage("Failed to send message to host: " + result + "\n" + Environment.StackTrace);
}
return netClient.SendMessage(msg.ToLidgren(netClient), deliveryMethod.ToLidgren());
}
#if DEBUG
@@ -241,4 +274,4 @@ namespace Barotrauma.Networking
}
#endif
}
}
}
@@ -1,13 +1,14 @@
using Barotrauma.Steam;
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Barotrauma.Steam;
using System.Threading;
namespace Barotrauma.Networking
{
class SteamP2PClientPeer : ClientPeer
internal sealed class SteamP2PClientPeer : ClientPeer
{
private bool isActive;
private readonly SteamId hostSteamId;
@@ -17,20 +18,20 @@ namespace Barotrauma.Networking
private long sentBytes, receivedBytes;
private List<IReadMessage> incomingInitializationMessages;
private List<IReadMessage> incomingDataMessages;
private readonly List<IncomingInitializationMessage> incomingInitializationMessages = new List<IncomingInitializationMessage>();
private readonly List<IReadMessage> incomingDataMessages = new List<IReadMessage>();
public SteamP2PClientPeer(SteamP2PEndpoint endpoint, Callbacks callbacks) : base(endpoint, callbacks, Option<int>.None())
{
ServerConnection = null;
isActive = false;
if (!(ServerEndpoint is SteamP2PEndpoint steamIdEndpoint))
{
throw new InvalidCastException("endPoint is not SteamId");
}
hostSteamId = steamIdEndpoint.SteamId;
}
@@ -55,16 +56,13 @@ namespace Barotrauma.Networking
ServerConnection = new SteamP2PConnection(hostSteamId);
ServerConnection.SetAccountInfo(new AccountInfo(hostSteamId));
incomingInitializationMessages = new List<IReadMessage>();
incomingDataMessages = new List<IReadMessage>();
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write((byte)DeliveryMethod.Reliable);
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
outMsg.Write((byte)ConnectionInitialization.ConnectionStarted);
Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable);
sentBytes += outMsg.LengthBytes;
var headers = new PeerPacketHeaders
{
DeliveryMethod = DeliveryMethod.Reliable,
PacketHeader = PacketHeader.IsConnectionInitializationStep,
Initialization = ConnectionInitialization.ConnectionStarted
};
SendMsgInternal(headers, null);
initializationStep = ConnectionInitialization.SteamTicketAndVersion;
@@ -78,6 +76,7 @@ namespace Barotrauma.Networking
private void OnIncomingConnection(Steamworks.SteamId steamId)
{
if (!isActive) { return; }
if (steamId == hostSteamId.Value)
{
Steamworks.SteamNetworking.AcceptP2PSessionWithUser(steamId);
@@ -86,16 +85,18 @@ namespace Barotrauma.Networking
initializationStep != ConnectionInitialization.ContentPackageOrder &&
initializationStep != ConnectionInitialization.Success)
{
DebugConsole.ThrowError($"Connection from incorrect SteamID was rejected: "+
$"expected {hostSteamId}," +
$"got {new SteamId(steamId)}");
DebugConsole.ThrowError("Connection from incorrect SteamID was rejected: " +
$"expected {hostSteamId}," +
$"got {new SteamId(steamId)}");
}
}
private void OnConnectionFailed(Steamworks.SteamId steamId, Steamworks.P2PSessionError error)
{
if (!isActive) { return; }
if (steamId != hostSteamId.Value) { return; }
Close($"SteamP2P connection failed: {error}");
callbacks.OnDisconnectMessageReceived.Invoke($"{DisconnectReason.SteamP2PError}/SteamP2P connection failed: {error}");
}
@@ -103,27 +104,31 @@ namespace Barotrauma.Networking
private void OnP2PData(ulong steamId, byte[] data, int dataLength)
{
if (!isActive) { return; }
if (steamId != hostSteamId.Value) { return; }
timeout = Screen.Selected == GameMain.GameScreen ?
NetworkConnection.TimeoutThresholdInGame :
NetworkConnection.TimeoutThreshold;
PacketHeader packetHeader = (PacketHeader)data[0];
timeout = Screen.Selected == GameMain.GameScreen
? NetworkConnection.TimeoutThresholdInGame
: NetworkConnection.TimeoutThreshold;
IReadMessage inc = new ReadOnlyMessage(data, false, 0, dataLength, ServerConnection);
var (deliveryMethod, packetHeader, initialization) = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
if (!packetHeader.IsServerMessage()) { return; }
if (packetHeader.IsConnectionInitializationStep())
if (packetHeader.IsConnectionInitializationStep() && initialization.HasValue)
{
ulong low = Lidgren.Network.NetBitWriter.ReadUInt32(data, 32, 8);
ulong high = Lidgren.Network.NetBitWriter.ReadUInt32(data, 32, 8 + 32);
ulong lobbyId = low + (high << 32);
var relayPacket = INetSerializableStruct.Read<SteamP2PInitializationRelayPacket>(inc);
Steam.SteamManager.JoinLobby(lobbyId, false);
IReadMessage inc = new ReadOnlyMessage(data, false, 1 + 8, dataLength - (1 + 8), ServerConnection);
SteamManager.JoinLobby(relayPacket.LobbyID, false);
if (initializationStep != ConnectionInitialization.Success)
{
incomingInitializationMessages.Add(inc);
incomingInitializationMessages.Add(new IncomingInitializationMessage
{
InitializationStep = initialization.Value,
Message = relayPacket.Message.GetReadMessage()
});
}
}
else if (packetHeader.IsHeartbeatMessage())
@@ -132,17 +137,14 @@ namespace Barotrauma.Networking
}
else if (packetHeader.IsDisconnectMessage())
{
IReadMessage inc = new ReadOnlyMessage(data, false, 1, dataLength - 1, ServerConnection);
string msg = inc.ReadString();
Close(msg);
callbacks.OnDisconnectMessageReceived.Invoke(msg);
PeerDisconnectPacket packet = INetSerializableStruct.Read<PeerDisconnectPacket>(inc);
Close(packet.Message);
callbacks.OnDisconnectMessageReceived.Invoke(packet.Message);
}
else
{
UInt16 length = Lidgren.Network.NetBitWriter.ReadUInt16(data, 16, 8);
IReadMessage inc = new ReadOnlyMessage(data, packetHeader.IsCompressed(), 3, length, ServerConnection);
incomingDataMessages.Add(inc);
var packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
incomingDataMessages.Add(packet.GetReadMessage());
}
}
@@ -154,6 +156,7 @@ namespace Barotrauma.Networking
{
timeout -= deltaTime;
}
heartbeatTimer -= deltaTime;
if (initializationStep != ConnectionInitialization.Password &&
@@ -163,20 +166,20 @@ namespace Barotrauma.Networking
connectionStatusTimer -= deltaTime;
if (connectionStatusTimer <= 0.0)
{
var state = Steamworks.SteamNetworking.GetP2PSessionState(hostSteamId.Value);
if (state == null)
if (Steamworks.SteamNetworking.GetP2PSessionState(hostSteamId.Value) is { } state)
{
if (state.P2PSessionError != Steamworks.P2PSessionError.None)
{
Close($"SteamP2P error code: {state.P2PSessionError}");
callbacks.OnDisconnectMessageReceived.Invoke($"{DisconnectReason.SteamP2PError}/SteamP2P error code: {state.P2PSessionError}");
}
}
else
{
Close("SteamP2P connection could not be established");
callbacks.OnDisconnectMessageReceived.Invoke(DisconnectReason.SteamP2PError.ToString());
}
else
{
if (state?.P2PSessionError != Steamworks.P2PSessionError.None)
{
Close($"SteamP2P error code: {state?.P2PSessionError}");
callbacks.OnDisconnectMessageReceived.Invoke($"{DisconnectReason.SteamP2PError}/SteamP2P error code: {state?.P2PSessionError}");
}
}
connectionStatusTimer = 1.0f;
}
}
@@ -184,11 +187,12 @@ namespace Barotrauma.Networking
for (int i = 0; i < 100; i++)
{
if (!Steamworks.SteamNetworking.IsP2PPacketAvailable()) { break; }
var packet = Steamworks.SteamNetworking.ReadP2PPacket();
if (packet.HasValue)
if (packet is { SteamId: var steamId, Data: var data })
{
OnP2PData(packet?.SteamId ?? 0, packet?.Data, packet?.Data.Length ?? 0);
receivedBytes += packet?.Data.Length ?? 0;
OnP2PData(steamId, data, data.Length);
receivedBytes += data.Length;
}
}
@@ -197,14 +201,13 @@ namespace Barotrauma.Networking
if (heartbeatTimer < 0.0)
{
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write((byte)DeliveryMethod.Unreliable);
outMsg.Write((byte)PacketHeader.IsHeartbeatMessage);
Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Unreliable);
sentBytes += outMsg.LengthBytes;
heartbeatTimer = 5.0;
var headers = new PeerPacketHeaders
{
DeliveryMethod = DeliveryMethod.Unreliable,
PacketHeader = PacketHeader.IsHeartbeatMessage,
Initialization = null
};
SendMsgInternal(headers, null);
}
if (timeout < 0.0)
@@ -238,7 +241,7 @@ namespace Barotrauma.Networking
}
else
{
foreach (IReadMessage inc in incomingInitializationMessages)
foreach (var inc in incomingInitializationMessages)
{
ReadConnectionInitializationStep(inc);
}
@@ -261,76 +264,40 @@ namespace Barotrauma.Networking
{
if (!isActive) { return; }
byte[] buf = new byte[msg.LengthBytes + 4];
buf[0] = (byte)deliveryMethod;
byte[] bufAux = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _);
byte[] bufAux = new byte[msg.LengthBytes];
msg.PrepareForSending(ref bufAux, compressPastThreshold, out bool isCompressed, out int length);
buf[1] = (byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None);
buf[2] = (byte)(length & 0xff);
buf[3] = (byte)((length >> 8) & 0xff);
Array.Copy(bufAux, 0, buf, 4, length);
Steamworks.P2PSend sendType;
switch (deliveryMethod)
var headers = new PeerPacketHeaders
{
case DeliveryMethod.Reliable:
case DeliveryMethod.ReliableOrdered:
//the documentation seems to suggest that the Reliable send type
//enforces packet order (TODO: verify)
sendType = Steamworks.P2PSend.Reliable;
break;
default:
sendType = Steamworks.P2PSend.Unreliable;
break;
}
if (length + 8 >= MsgConstants.MTU)
DeliveryMethod = deliveryMethod,
PacketHeader = isCompressed ? PacketHeader.IsCompressed : PacketHeader.None,
Initialization = null
};
var body = new PeerPacketMessage
{
DebugConsole.Log("WARNING: message length comes close to exceeding MTU, forcing reliable send (" + length.ToString() + " bytes)");
sendType = Steamworks.P2PSend.Reliable;
}
Buffer = bufAux
};
heartbeatTimer = 5.0;
// Using an extra local method here to reduce chance of error whenever we need to change this
void performSend() => SendMsgInternal(headers, body);
#if DEBUG
CoroutineManager.Invoke(() =>
{
if (GameMain.Client == null) { return; }
if (Rand.Range(0.0f, 1.0f) < GameMain.Client.SimulatedLoss && sendType != Steamworks.P2PSend.Reliable) { return; }
int count = Rand.Range(0.0f, 1.0f) < GameMain.Client.SimulatedDuplicatesChance ? 2 : 1;
for (int i = 0; i < count; i++)
{
Send(buf, length + 4, sendType);
}
},
GameMain.Client.SimulatedMinimumLatency + Rand.Range(0.0f, GameMain.Client.SimulatedRandomLatency));
#else
Send(buf, length + 4, sendType);
#endif
}
if (GameMain.Client == null) { return; }
private void Send(byte[] buf, int length, Steamworks.P2PSend sendType)
{
bool successSend = Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, buf, length + 4, 0, sendType);
sentBytes += length + 4;
if (!successSend)
{
if (sendType != Steamworks.P2PSend.Reliable)
{
DebugConsole.Log("WARNING: message couldn't be sent unreliably, forcing reliable send (" + length.ToString() + " bytes)");
sendType = Steamworks.P2PSend.Reliable;
successSend = Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, buf, length + 4, 0, sendType);
sentBytes += length + 4;
}
if (!successSend)
{
DebugConsole.AddWarning("Failed to send message to remote peer! (" + length.ToString() + " bytes)");
}
}
if (Rand.Range(0.0f, 1.0f) < GameMain.Client.SimulatedLoss && deliveryMethod is DeliveryMethod.Unreliable) { return; }
int count = Rand.Range(0.0f, 1.0f) < GameMain.Client.SimulatedDuplicatesChance ? 2 : 1;
for (int i = 0; i < count; i++)
{
performSend();
}
},
GameMain.Client.SimulatedMinimumLatency + Rand.Range(0.0f, GameMain.Client.SimulatedRandomLatency));
#else
performSend();
#endif
}
public override void SendPassword(string password)
@@ -338,20 +305,22 @@ namespace Barotrauma.Networking
if (!isActive) { return; }
if (initializationStep != ConnectionInitialization.Password) { return; }
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write((byte)DeliveryMethod.Reliable);
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
outMsg.Write((byte)ConnectionInitialization.Password);
byte[] saltedPw = ServerSettings.SaltPassword(Encoding.UTF8.GetBytes(password), passwordSalt);
outMsg.Write((byte)saltedPw.Length);
outMsg.Write(saltedPw, 0, saltedPw.Length);
heartbeatTimer = 5.0;
Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable);
sentBytes += outMsg.LengthBytes;
var headers = new PeerPacketHeaders
{
DeliveryMethod = DeliveryMethod.Reliable,
PacketHeader = PacketHeader.IsConnectionInitializationStep,
Initialization = ConnectionInitialization.Password
};
var body = new ClientPeerPasswordPacket
{
Password = ServerSettings.SaltPassword(Encoding.UTF8.GetBytes(password), passwordSalt)
};
SendMsgInternal(headers, body);
}
public override void Close(string msg = null, bool disableReconnect = false)
public override void Close(string? msg = null, bool disableReconnect = false)
{
if (!isActive) { return; }
@@ -359,54 +328,59 @@ namespace Barotrauma.Networking
isActive = false;
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write((byte)DeliveryMethod.Reliable);
outMsg.Write((byte)PacketHeader.IsDisconnectMessage);
outMsg.Write(msg ?? "Disconnected");
var headers = new PeerPacketHeaders
{
DeliveryMethod = DeliveryMethod.Reliable,
PacketHeader = PacketHeader.IsDisconnectMessage,
Initialization = null
};
var body = new PeerDisconnectPacket
{
Message = msg ?? "Disconnected"
};
try
{
Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable);
sentBytes += outMsg.LengthBytes;
}
catch (Exception e)
{
DebugConsole.ThrowError("Failed to send a disconnect message to the server using SteamP2P.", e);
}
SendMsgInternal(headers, body);
Thread.Sleep(100);
Steamworks.SteamNetworking.ResetActions();
Steamworks.SteamNetworking.CloseP2PSessionWithUser(hostSteamId.Value);
steamAuthTicket?.Cancel(); steamAuthTicket = null;
steamAuthTicket?.Cancel();
steamAuthTicket = null;
callbacks.OnDisconnect.Invoke(disableReconnect);
}
protected override void SendMsgInternal(DeliveryMethod deliveryMethod, IWriteMessage msg)
protected override void SendMsgInternal(PeerPacketHeaders headers, INetSerializableStruct? body)
{
Steamworks.P2PSend sendType;
switch (deliveryMethod)
IWriteMessage msgToSend = new WriteOnlyMessage();
msgToSend.WriteNetSerializableStruct(headers);
body?.Write(msgToSend);
ForwardToSteamP2P(msgToSend, headers.DeliveryMethod);
}
private void ForwardToSteamP2P(IWriteMessage msg, DeliveryMethod deliveryMethod)
{
heartbeatTimer = 5.0;
int length = msg.LengthBytes;
bool successSend = Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, msg.Buffer, length, 0, deliveryMethod.ToSteam());
sentBytes += length;
if (successSend) { return; }
if (deliveryMethod is DeliveryMethod.Unreliable)
{
case DeliveryMethod.Reliable:
case DeliveryMethod.ReliableOrdered:
//the documentation seems to suggest that the Reliable send type
//enforces packet order (TODO: verify)
sendType = Steamworks.P2PSend.Reliable;
break;
default:
sendType = Steamworks.P2PSend.Unreliable;
break;
DebugConsole.Log($"WARNING: message couldn't be sent unreliably, forcing reliable send ({length} bytes)");
successSend = Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, msg.Buffer, length, 0, DeliveryMethod.Reliable.ToSteam());
sentBytes += length;
}
IWriteMessage msgToSend = new WriteOnlyMessage();
msgToSend.Write((byte)deliveryMethod);
msgToSend.Write(msg.Buffer, 0, msg.LengthBytes);
heartbeatTimer = 5.0;
Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, msgToSend.Buffer, msgToSend.LengthBytes, 0, sendType);
sentBytes += msg.LengthBytes;
if (!successSend)
{
DebugConsole.AddWarning($"Failed to send message to remote peer! ({length} bytes)");
}
}
#if DEBUG
@@ -416,4 +390,4 @@ namespace Barotrauma.Networking
}
#endif
}
}
}
@@ -1,38 +1,46 @@
using Barotrauma.Extensions;
#nullable enable
using Barotrauma.Steam;
using System;
using System.Collections.Generic;
using System.Threading;
using Barotrauma.Extensions;
namespace Barotrauma.Networking
{
class SteamP2POwnerPeer : ClientPeer
sealed class SteamP2POwnerPeer : ClientPeer
{
private bool isActive;
private readonly SteamId selfSteamID;
private UInt64 ownerKey64 => unchecked((UInt64)ownerKey.Fallback(0));
private SteamId ReadSteamId(IReadMessage inc)
=> new SteamId(inc.ReadUInt64() ^ ownerKey64);
private void WriteSteamId(IWriteMessage msg, SteamId val)
=> msg.Write(val.Value ^ ownerKey64);
private SteamId ReadSteamId(IReadMessage inc) => new SteamId(inc.ReadUInt64() ^ ownerKey64);
private void WriteSteamId(IWriteMessage msg, SteamId val) => msg.WriteUInt64(val.Value ^ ownerKey64);
private long sentBytes, receivedBytes;
class RemotePeer
private sealed class RemotePeer
{
public SteamId SteamId;
public readonly SteamId SteamId;
public Option<SteamId> OwnerSteamId;
public double? DisconnectTime;
public bool Authenticating;
public bool Authenticated;
public class UnauthedMessage
public readonly struct UnauthedMessage
{
public DeliveryMethod DeliveryMethod;
public IWriteMessage Message;
public readonly SteamId Sender;
public readonly byte[] Bytes;
public readonly int Length;
public UnauthedMessage(SteamId sender, byte[] bytes)
{
Sender = sender;
Bytes = bytes;
Length = bytes.Length;
}
}
public readonly List<UnauthedMessage> UnauthedMessages;
public RemotePeer(SteamId steamId)
@@ -45,9 +53,9 @@ namespace Barotrauma.Networking
UnauthedMessages = new List<UnauthedMessage>();
}
}
List<RemotePeer> remotePeers;
private List<RemotePeer> remotePeers = null!;
public SteamP2POwnerPeer(Callbacks callbacks, int ownerKey) : base(new PipeEndpoint(), callbacks, Option<int>.Some(ownerKey))
{
@@ -84,8 +92,8 @@ namespace Barotrauma.Networking
private void OnAuthChange(Steamworks.SteamId steamId, Steamworks.SteamId ownerId, Steamworks.AuthResponse status)
{
RemotePeer remotePeer = remotePeers.Find(p => p.SteamId.Value == steamId);
DebugConsole.Log(steamId + " validation: " + status + ", " + (remotePeer != null));
RemotePeer? remotePeer = remotePeers.Find(p => p.SteamId.Value == steamId);
DebugConsole.Log($"{steamId} validation: {status}, {remotePeer != null}");
if (remotePeer == null) { return; }
@@ -93,36 +101,32 @@ namespace Barotrauma.Networking
{
if (status != Steamworks.AuthResponse.OK)
{
DisconnectPeer(remotePeer, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam authentication status changed: " + status.ToString());
DisconnectPeer(remotePeer, $"{DisconnectReason.SteamAuthenticationFailed}/ Steam authentication status changed: {status}");
}
return;
}
if (status == Steamworks.AuthResponse.OK)
{
remotePeer.OwnerSteamId = Option<SteamId>.Some(new SteamId(ownerId));
SteamId ownerSteamId = new SteamId(ownerId);
remotePeer.OwnerSteamId = Option<SteamId>.Some(ownerSteamId);
remotePeer.Authenticated = true;
remotePeer.Authenticating = false;
foreach (var msg in remotePeer.UnauthedMessages)
foreach (var unauthedMessage in remotePeer.UnauthedMessages)
{
//rewrite the owner id before
//forwarding the messages to
//the server, since it's only
//known now
int prevBitPosition = msg.Message.BitPosition;
msg.Message.BitPosition = sizeof(ulong) * 8;
WriteSteamId(msg.Message, new SteamId(ownerId));
msg.Message.BitPosition = prevBitPosition;
byte[] msgToSend = (byte[])msg.Message.Buffer.Clone();
Array.Resize(ref msgToSend, msg.Message.LengthBytes);
ChildServerRelay.Write(msgToSend);
IWriteMessage msg = new WriteOnlyMessage();
WriteSteamId(msg, unauthedMessage.Sender);
WriteSteamId(msg, ownerSteamId);
msg.WriteBytes(unauthedMessage.Bytes, 0, unauthedMessage.Length);
ForwardToServerProcess(msg);
}
remotePeer.UnauthedMessages.Clear();
}
else
{
DisconnectPeer(remotePeer, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam authentication failed: " + status.ToString());
return;
DisconnectPeer(remotePeer, $"{DisconnectReason.SteamAuthenticationFailed}/ Steam authentication failed: {status}");
}
}
@@ -138,58 +142,54 @@ namespace Barotrauma.Networking
Steamworks.SteamNetworking.AcceptP2PSessionWithUser(steamId); //accept all connections, the server will figure things out later
}
private void OnP2PData(ulong steamId, byte[] data, int dataLength, int _)
private void OnP2PData(ulong steamId, IReadMessage inc)
{
if (!isActive) { return; }
RemotePeer remotePeer = remotePeers.Find(p => p.SteamId.Value == steamId);
RemotePeer? remotePeer = remotePeers.Find(p => p.SteamId.Value == steamId);
if (remotePeer == null) { return; }
if (remotePeer.DisconnectTime != null) { return; }
IWriteMessage outMsg = new WriteOnlyMessage();
var steamUserId = new SteamId(steamId);
WriteSteamId(outMsg, steamUserId);
WriteSteamId(outMsg, remotePeer.OwnerSteamId.Fallback(steamUserId));
outMsg.Write(data, 1, dataLength - 1);
DeliveryMethod deliveryMethod = (DeliveryMethod)data[0];
PacketHeader packetHeader = (PacketHeader)data[1];
var peerPacketHeaders = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
PacketHeader packetHeader = peerPacketHeaders.PacketHeader;
if (!remotePeer.Authenticated && !remotePeer.Authenticating && packetHeader.IsConnectionInitializationStep())
{
remotePeer.DisconnectTime = null;
IReadMessage authMsg = new ReadOnlyMessage(data, packetHeader.IsCompressed(), 2, dataLength - 2, null);
ConnectionInitialization initializationStep = (ConnectionInitialization)authMsg.ReadByte();
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion)
ConnectionInitialization initialization = peerPacketHeaders.Initialization ?? throw new Exception("Initialization step missing");
if (initialization == ConnectionInitialization.SteamTicketAndVersion)
{
remotePeer.Authenticating = true;
authMsg.ReadString(); //skip name
authMsg.ReadInt32(); //skip owner key
authMsg.ReadUInt64(); //skip steamid
UInt16 ticketLength = authMsg.ReadUInt16();
byte[] ticket = authMsg.ReadBytes(ticketLength);
Steamworks.BeginAuthResult authSessionStartState = Steam.SteamManager.StartAuthSession(ticket, steamId);
var packet = INetSerializableStruct.Read<ClientSteamTicketAndVersionPacket>(inc);
packet.SteamAuthTicket.TryUnwrap(out byte[] ticket);
Steamworks.BeginAuthResult authSessionStartState = SteamManager.StartAuthSession(ticket, steamId);
if (authSessionStartState != Steamworks.BeginAuthResult.OK)
{
DisconnectPeer(remotePeer, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam auth session failed to start: " + authSessionStartState.ToString());
DisconnectPeer(remotePeer, $"{DisconnectReason.SteamAuthenticationFailed}/ Steam auth session failed to start: {authSessionStartState}");
return;
}
}
}
var steamUserId = new SteamId(steamId);
if (remotePeer.Authenticating)
{
remotePeer.UnauthedMessages.Add(new RemotePeer.UnauthedMessage() { DeliveryMethod = deliveryMethod, Message = outMsg });
remotePeer.UnauthedMessages.Add(new RemotePeer.UnauthedMessage(steamUserId, inc.Buffer));
}
else
{
byte[] msgToSend = (byte[])outMsg.Buffer.Clone();
Array.Resize(ref msgToSend, outMsg.LengthBytes);
ChildServerRelay.Write(msgToSend);
IWriteMessage outMsg = new WriteOnlyMessage();
WriteSteamId(outMsg, steamUserId);
WriteSteamId(outMsg, remotePeer.OwnerSteamId.Fallback(steamUserId));
outMsg.WriteBytes(inc.Buffer, 0, inc.LengthBytes);
ForwardToServerProcess(outMsg);
}
}
@@ -201,7 +201,11 @@ namespace Barotrauma.Networking
{
Close();
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage);
msgBox.Buttons[0].OnClicked += (btn, obj) => { GameMain.MainMenuScreen.Select(); return false; };
msgBox.Buttons[0].OnClicked += (btn, obj) =>
{
GameMain.MainMenuScreen.Select();
return false;
};
return;
}
@@ -216,11 +220,12 @@ namespace Barotrauma.Networking
for (int i = 0; i < 100; i++)
{
if (!Steamworks.SteamNetworking.IsP2PPacketAvailable()) { break; }
var packet = Steamworks.SteamNetworking.ReadP2PPacket();
if (packet.HasValue)
if (packet is { SteamId: var steamId, Data: var data })
{
OnP2PData(packet?.SteamId ?? 0, packet?.Data, packet?.Data.Length ?? 0, 0);
receivedBytes += packet?.Data.Length ?? 0;
OnP2PData(steamId, new ReadWriteMessage(data, 0, data.Length * 8, false));
receivedBytes += data.Length;
}
}
@@ -240,155 +245,144 @@ namespace Barotrauma.Networking
if (!isActive) { return; }
SteamId recipientSteamId = ReadSteamId(inc);
DeliveryMethod deliveryMethod = (DeliveryMethod)inc.ReadByte();
int p2pDataStart = inc.BytePosition;
PacketHeader packetHeader = (PacketHeader)inc.ReadByte();
var peerPacketHeaders = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
if (recipientSteamId != selfSteamID)
{
if (!packetHeader.IsServerMessage())
{
DebugConsole.ThrowError("Received non-server message meant for remote peer");
return;
}
RemotePeer peer = remotePeers.Find(p => p.SteamId == recipientSteamId);
if (peer == null) { return; }
if (packetHeader.IsDisconnectMessage())
{
DisconnectPeer(peer, inc.ReadString());
return;
}
Steamworks.P2PSend sendType;
switch (deliveryMethod)
{
case DeliveryMethod.Reliable:
case DeliveryMethod.ReliableOrdered:
//the documentation seems to suggest that the
//Reliable send type enforces packet order
sendType = Steamworks.P2PSend.Reliable;
break;
default:
sendType = Steamworks.P2PSend.Unreliable;
break;
}
byte[] p2pData;
if (packetHeader.IsConnectionInitializationStep())
{
p2pData = new byte[inc.LengthBytes - p2pDataStart + 8];
p2pData[0] = inc.Buffer[p2pDataStart];
Lidgren.Network.NetBitWriter.WriteUInt64(SteamManager.CurrentLobbyID, 8 * 8, p2pData, 1 * 8);
Array.Copy(inc.Buffer, p2pDataStart+1, p2pData, 1 + 8, inc.LengthBytes - p2pDataStart - 1);
}
else
{
p2pData = new byte[inc.LengthBytes - p2pDataStart];
Array.Copy(inc.Buffer, p2pDataStart, p2pData, 0, p2pData.Length);
if (!packetHeader.IsHeartbeatMessage() && !packetHeader.IsDisconnectMessage())
{
UInt16 length = Lidgren.Network.NetBitWriter.ReadUInt16(p2pData, 16, 8);
if (length > p2pData.Length - 2)
{
string errorMsg = $"Length written in message to send to client is larger than buffer size ({length} > {p2pData.Length - 2})";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce(
"SteamP2POwnerPeerLengthValidationFail",
GameAnalyticsManager.ErrorSeverity.Error,
errorMsg);
}
}
}
if (p2pData.Length + 4 >= MsgConstants.MTU)
{
DebugConsole.Log("WARNING: message length comes close to exceeding MTU, forcing reliable send (" + p2pData.Length.ToString() + " bytes)");
sendType = Steamworks.P2PSend.Reliable;
}
bool successSend = Steamworks.SteamNetworking.SendP2PPacket(recipientSteamId.Value, p2pData, p2pData.Length, 0, sendType);
sentBytes += p2pData.Length;
if (!successSend)
{
if (sendType != Steamworks.P2PSend.Reliable)
{
DebugConsole.Log("WARNING: message couldn't be sent unreliably, forcing reliable send (" + p2pData.Length.ToString() + " bytes)");
sendType = Steamworks.P2PSend.Reliable;
successSend = Steamworks.SteamNetworking.SendP2PPacket(recipientSteamId.Value, p2pData, p2pData.Length, 0, sendType);
sentBytes += p2pData.Length;
}
if (!successSend)
{
DebugConsole.AddWarning("Failed to send message to remote peer! (" + p2pData.Length.ToString() + " bytes)");
}
}
HandleMessageForRemotePeer(peerPacketHeaders, recipientSteamId, inc);
}
else
{
if (packetHeader.IsDisconnectMessage())
{
DebugConsole.ThrowError("Received disconnect message from owned server");
return;
}
if (!packetHeader.IsServerMessage())
{
DebugConsole.ThrowError("Received non-server message from owned server");
return;
}
if (packetHeader.IsHeartbeatMessage())
{
return; //no timeout since we're using pipes, ignore this message
}
if (packetHeader.IsConnectionInitializationStep())
{
IWriteMessage outMsg = new WriteOnlyMessage();
WriteSteamId(outMsg, selfSteamID);
WriteSteamId(outMsg, selfSteamID);
outMsg.Write((byte)(PacketHeader.IsConnectionInitializationStep));
outMsg.Write(GameMain.Client.Name);
byte[] msgToSend = (byte[])outMsg.Buffer.Clone();
Array.Resize(ref msgToSend, outMsg.LengthBytes);
ChildServerRelay.Write(msgToSend);
return;
}
else
{
if (initializationStep != ConnectionInitialization.Success)
{
callbacks.OnInitializationComplete.Invoke();
initializationStep = ConnectionInitialization.Success;
}
UInt16 length = inc.ReadUInt16();
IReadMessage msg = new ReadOnlyMessage(inc.Buffer, packetHeader.IsCompressed(), inc.BytePosition, length, ServerConnection);
callbacks.OnMessageReceived.Invoke(msg);
return;
}
HandleMessageForOwner(peerPacketHeaders, inc);
}
}
private static byte[] GetRemainingBytes(IReadMessage msg)
{
return msg.Buffer[msg.BytePosition..msg.LengthBytes];
}
private void HandleMessageForRemotePeer(PeerPacketHeaders peerPacketHeaders, SteamId recipientSteamId, IReadMessage inc)
{
var (deliveryMethod, packetHeader, initialization) = peerPacketHeaders;
if (!packetHeader.IsServerMessage())
{
DebugConsole.ThrowError("Received non-server message meant for remote peer");
return;
}
RemotePeer? peer = remotePeers.Find(p => p.SteamId == recipientSteamId);
if (peer is null) { return; }
if (packetHeader.IsDisconnectMessage())
{
var packet = INetSerializableStruct.Read<PeerDisconnectPacket>(inc);
DisconnectPeer(peer, packet.Message);
return;
}
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.WriteNetSerializableStruct(new PeerPacketHeaders
{
DeliveryMethod = deliveryMethod,
PacketHeader = packetHeader,
Initialization = initialization
});
if (packetHeader.IsConnectionInitializationStep())
{
var initRelayPacket = new SteamP2PInitializationRelayPacket
{
LobbyID = SteamManager.CurrentLobbyID,
Message = new PeerPacketMessage
{
Buffer = GetRemainingBytes(inc)
}
};
outMsg.WriteNetSerializableStruct(initRelayPacket);
}
else
{
byte[] userMessage = GetRemainingBytes(inc);
outMsg.WriteBytes(userMessage, 0, userMessage.Length);
}
ForwardToRemotePeer(deliveryMethod, recipientSteamId, outMsg);
}
private void HandleMessageForOwner(PeerPacketHeaders peerPacketHeaders, IReadMessage inc)
{
var (_, packetHeader, _) = peerPacketHeaders;
if (packetHeader.IsDisconnectMessage())
{
DebugConsole.ThrowError("Received disconnect message from owned server");
return;
}
if (!packetHeader.IsServerMessage())
{
DebugConsole.ThrowError("Received non-server message from owned server");
return;
}
if (packetHeader.IsHeartbeatMessage())
{
return; //no timeout since we're using pipes, ignore this message
}
if (packetHeader.IsConnectionInitializationStep())
{
IWriteMessage outMsg = new WriteOnlyMessage();
WriteSteamId(outMsg, selfSteamID);
WriteSteamId(outMsg, selfSteamID);
outMsg.WriteNetSerializableStruct(new PeerPacketHeaders
{
DeliveryMethod = DeliveryMethod.Reliable,
PacketHeader = PacketHeader.IsConnectionInitializationStep,
Initialization = ConnectionInitialization.SteamTicketAndVersion
});
outMsg.WriteNetSerializableStruct(new SteamP2PInitializationOwnerPacket
{
OwnerName = GameMain.Client.Name
});
ForwardToServerProcess(outMsg);
}
else
{
if (initializationStep != ConnectionInitialization.Success)
{
callbacks.OnInitializationComplete.Invoke();
initializationStep = ConnectionInitialization.Success;
}
PeerPacketMessage packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
IReadMessage msg = new ReadOnlyMessage(packet.Buffer, packetHeader.IsCompressed(), 0, packet.Length, ServerConnection);
callbacks.OnMessageReceived.Invoke(msg);
}
}
private void DisconnectPeer(RemotePeer peer, string msg)
{
if (!string.IsNullOrWhiteSpace(msg))
{
if (peer.DisconnectTime == null)
{
peer.DisconnectTime = Timing.TotalTime + 1.0;
}
peer.DisconnectTime ??= Timing.TotalTime + 1.0;
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write((byte)(PacketHeader.IsServerMessage | PacketHeader.IsDisconnectMessage));
outMsg.Write(msg);
outMsg.WriteNetSerializableStruct(new PeerPacketHeaders
{
DeliveryMethod = DeliveryMethod.Reliable,
PacketHeader = PacketHeader.IsServerMessage | PacketHeader.IsDisconnectMessage
});
outMsg.WriteNetSerializableStruct(new PeerDisconnectPacket
{
Message = msg
});
Steamworks.SteamNetworking.SendP2PPacket(peer.SteamId.Value, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable);
sentBytes += outMsg.LengthBytes;
}
@@ -406,10 +400,10 @@ namespace Barotrauma.Networking
public override void SendPassword(string password)
{
return; //owner doesn't send passwords
//owner doesn't send passwords
}
public override void Close(string msg = null, bool disableReconnect = false)
public override void Close(string? msg = null, bool disableReconnect = false)
{
if (!isActive) { return; }
@@ -441,25 +435,62 @@ namespace Barotrauma.Networking
if (!isActive) { return; }
IWriteMessage msgToSend = new WriteOnlyMessage();
byte[] msgData = new byte[msg.LengthBytes];
msg.PrepareForSending(ref msgData, compressPastThreshold, out bool isCompressed, out int length);
byte[] msgData = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _);
WriteSteamId(msgToSend, selfSteamID);
WriteSteamId(msgToSend, selfSteamID);
msgToSend.Write((byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None));
msgToSend.Write((UInt16)length);
msgToSend.Write(msgData, 0, length);
byte[] bufToSend = (byte[])msgToSend.Buffer.Clone();
Array.Resize(ref bufToSend, msgToSend.LengthBytes);
ChildServerRelay.Write(bufToSend);
msgToSend.WriteNetSerializableStruct(new PeerPacketHeaders
{
DeliveryMethod = deliveryMethod,
PacketHeader = isCompressed ? PacketHeader.IsCompressed : PacketHeader.None
});
msgToSend.WriteNetSerializableStruct(new PeerPacketMessage
{
Buffer = msgData
});
ForwardToServerProcess(msgToSend);
}
protected override void SendMsgInternal(DeliveryMethod deliveryMethod, IWriteMessage msg)
protected override void SendMsgInternal(PeerPacketHeaders headers, INetSerializableStruct? body)
{
//not currently used by SteamP2POwnerPeer
throw new NotImplementedException();
}
private static void ForwardToServerProcess(IWriteMessage msg)
{
byte[] bufToSend = new byte[msg.LengthBytes];
msg.Buffer[..msg.LengthBytes].CopyTo(bufToSend.AsSpan());
ChildServerRelay.Write(bufToSend);
}
private void ForwardToRemotePeer(DeliveryMethod deliveryMethod, SteamId recipent, IWriteMessage outMsg)
{
byte[] buf = outMsg.PrepareForSending(compressPastThreshold: false, out _, out int length);
if (length + 4 >= MsgConstants.MTU)
{
DebugConsole.Log($"WARNING: message length comes close to exceeding MTU, forcing reliable send ({length} bytes)");
deliveryMethod = DeliveryMethod.Reliable;
}
bool successSend = Steamworks.SteamNetworking.SendP2PPacket(recipent.Value, buf, length, 0, deliveryMethod.ToSteam());
sentBytes += length;
if (successSend) { return; }
if (deliveryMethod is DeliveryMethod.Unreliable)
{
DebugConsole.Log($"WARNING: message couldn't be sent unreliably, forcing reliable send ({length} bytes)");
successSend = Steamworks.SteamNetworking.SendP2PPacket(recipent.Value, buf, length, 0, DeliveryMethod.Reliable.ToSteam());
sentBytes += length;
}
if (!successSend)
{
DebugConsole.AddWarning($"Failed to send message to remote peer! ({length} bytes)");
}
}
#if DEBUG
public override void ForceTimeOut()
{
@@ -467,4 +498,4 @@ namespace Barotrauma.Networking
}
#endif
}
}
}
@@ -44,14 +44,19 @@ namespace Barotrauma.Networking
if (!UseRespawnPrompt) { return; }
if (CoroutineManager.IsCoroutineRunning(respawnPromptCoroutine) || GUIMessageBox.MessageBoxes.Any(mb => mb.UserData as string == "respawnquestionprompt"))
{
return;
return;
}
respawnPromptCoroutine = CoroutineManager.Invoke(() =>
{
if (Character.Controlled != null || (!(GameMain.GameSession?.IsRunning ?? false))) { return; }
LocalizedString text =
TextManager.GetWithVariable("respawnskillpenalty", "[percentage]", ((int)(SkillReductionOnDeath * 100)).ToString())
+ "\n\n" + TextManager.Get("respawnquestionprompt");
var respawnPrompt = new GUIMessageBox(
TextManager.Get("tutorial.tryagainheader"), TextManager.Get("respawnquestionprompt"),
TextManager.Get("tutorial.tryagainheader"), text,
new LocalizedString[] { TextManager.Get("respawnquestionpromptrespawn"), TextManager.Get("respawnquestionpromptwait") })
{
UserData = "respawnquestionprompt"
@@ -348,7 +348,7 @@ namespace Barotrauma.Networking
ServerName = element.GetAttributeString("ServerName", ""),
ServerMessage = element.GetAttributeString("ServerMessage", ""),
Endpoint = endpoint,
QueryPort = element.GetAttributeInt("QueryPort", 0),
QueryPort = !string.IsNullOrEmpty(element.GetAttributeString("QueryPort", string.Empty)) ? element.GetAttributeInt("QueryPort", 0) : 0,
GameMode = element.GetAttributeIdentifier("GameMode", Identifier.Empty),
GameVersion = element.GetAttributeString("GameVersion", ""),
MaxPlayers = Math.Min(element.GetAttributeInt("MaxPlayers", 0), NetConfig.MaxPlayers),
@@ -182,9 +182,9 @@ namespace Barotrauma.Networking
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write((byte)ClientPacketHeader.SERVER_SETTINGS);
outMsg.WriteByte((byte)ClientPacketHeader.SERVER_SETTINGS);
outMsg.Write((byte)dataToSend);
outMsg.WriteByte((byte)dataToSend);
if (dataToSend.HasFlag(NetFlags.Name))
{
@@ -192,7 +192,7 @@ namespace Barotrauma.Networking
{
ServerName = GameMain.NetLobbyScreen.ServerName.Text;
}
outMsg.Write(ServerName);
outMsg.WriteString(ServerName);
}
if (dataToSend.HasFlag(NetFlags.Message))
@@ -201,7 +201,7 @@ namespace Barotrauma.Networking
{
ServerMessageText = GameMain.NetLobbyScreen.ServerMessage.Text;
}
outMsg.Write(ServerMessageText);
outMsg.WriteString(ServerMessageText);
}
if (dataToSend.HasFlag(NetFlags.Properties))
@@ -213,15 +213,15 @@ namespace Barotrauma.Networking
UInt32 count = (UInt32)changedProperties.Count();
bool changedMonsterSettings = tempMonsterEnabled != null && tempMonsterEnabled.Any(p => p.Value != MonsterEnabled[p.Key]);
outMsg.Write(count);
outMsg.WriteUInt32(count);
foreach (KeyValuePair<UInt32, NetPropertyData> prop in changedProperties)
{
DebugConsole.NewMessage(prop.Value.Name.Value, Color.Lime);
outMsg.Write(prop.Key);
outMsg.WriteUInt32(prop.Key);
prop.Value.Write(outMsg, prop.Value.GUIComponentValue);
}
outMsg.Write(changedMonsterSettings); outMsg.WritePadBits();
outMsg.WriteBoolean(changedMonsterSettings); outMsg.WritePadBits();
if (changedMonsterSettings) WriteMonsterEnabled(outMsg, tempMonsterEnabled);
BanList.ClientAdminWrite(outMsg);
}
@@ -235,23 +235,23 @@ namespace Barotrauma.Networking
{
outMsg.WriteRangedInteger(missionTypeOr ?? (int)Barotrauma.MissionType.None, 0, (int)Barotrauma.MissionType.All);
outMsg.WriteRangedInteger(missionTypeAnd ?? (int)Barotrauma.MissionType.All, 0, (int)Barotrauma.MissionType.All);
outMsg.Write((byte)(traitorSetting + 1));
outMsg.Write((byte)(botCount + 1));
outMsg.Write((byte)(botSpawnMode + 1));
outMsg.WriteByte((byte)(traitorSetting + 1));
outMsg.WriteByte((byte)(botCount + 1));
outMsg.WriteByte((byte)(botSpawnMode + 1));
outMsg.Write(levelDifficulty ?? -1000.0f);
outMsg.WriteSingle(levelDifficulty ?? -1000.0f);
outMsg.Write(useRespawnShuttle ?? UseRespawnShuttle);
outMsg.WriteBoolean(useRespawnShuttle ?? UseRespawnShuttle);
outMsg.Write(autoRestart != null);
outMsg.Write(autoRestart ?? false);
outMsg.WriteBoolean(autoRestart != null);
outMsg.WriteBoolean(autoRestart ?? false);
outMsg.WritePadBits();
}
if (dataToSend.HasFlag(NetFlags.LevelSeed))
{
outMsg.Write(GameMain.NetLobbyScreen.SeedBox.Text);
outMsg.WriteString(GameMain.NetLobbyScreen.SeedBox.Text);
}
GameMain.Client.ClientPeer.Send(outMsg, DeliveryMethod.Reliable);
@@ -4,7 +4,6 @@ using Microsoft.Xna.Framework;
using OpenAL;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
@@ -22,13 +21,13 @@ namespace Barotrauma.Networking
public static IReadOnlyList<string> CaptureDeviceNames =>
Alc.GetStringList(IntPtr.Zero, OpenAL.Alc.CaptureDeviceSpecifier);
private IntPtr captureDevice;
private readonly IntPtr captureDevice;
private Thread captureThread;
private bool capturing;
private OpusEncoder encoder;
private readonly OpusEncoder encoder;
public double LastdB
{
@@ -171,8 +170,8 @@ namespace Barotrauma.Networking
}
IntPtr nativeBuffer;
short[] uncompressedBuffer = new short[VoipConfig.BUFFER_SIZE];
short[] prevUncompressedBuffer = new short[VoipConfig.BUFFER_SIZE];
readonly short[] uncompressedBuffer = new short[VoipConfig.BUFFER_SIZE];
readonly short[] prevUncompressedBuffer = new short[VoipConfig.BUFFER_SIZE];
bool prevCaptured = true;
int captureTimer;
@@ -227,16 +226,20 @@ namespace Barotrauma.Networking
bool allowEnqueue = overrideSound != null;
if (GameMain.WindowActive && SettingsMenu.Instance is null)
{
bool usingActiveMode = PlayerInput.KeyDown(InputType.Voice);
bool usingLocalMode = PlayerInput.KeyDown(InputType.LocalVoice);
bool usingRadioMode = PlayerInput.KeyDown(InputType.RadioVoice);
bool pttDown = (usingActiveMode || usingLocalMode || usingRadioMode) && GUI.KeyboardDispatcher.Subscriber == null;
if (pttDown || captureTimer <= 0)
{
ForceLocal = (usingActiveMode && GameMain.ActiveChatMode == ChatMode.Local) || usingLocalMode;
}
if (GameSettings.CurrentConfig.Audio.VoiceSetting == VoiceMode.Activity)
{
bool pttDown = (usingLocalMode || usingRadioMode) && GUI.KeyboardDispatcher.Subscriber == null;
if (pttDown)
{
ForceLocal = usingLocalMode;
}
//in Activity mode, we default to the active mode UNLESS a specific ptt key is held
else
{
ForceLocal = GameMain.ActiveChatMode == ChatMode.Local;
}
if (dB > GameSettings.CurrentConfig.Audio.NoiseGateThreshold)
{
allowEnqueue = true;
@@ -244,6 +247,13 @@ namespace Barotrauma.Networking
}
else if (GameSettings.CurrentConfig.Audio.VoiceSetting == VoiceMode.PushToTalk)
{
//in push-to-talk mode, InputType.Voice uses the active chat mode
bool usingActiveMode = PlayerInput.KeyDown(InputType.Voice);
bool pttDown = (usingActiveMode || usingLocalMode || usingRadioMode) && GUI.KeyboardDispatcher.Subscriber == null;
if (pttDown || captureTimer <= 0)
{
ForceLocal = (usingActiveMode && GameMain.ActiveChatMode == ChatMode.Local) || usingLocalMode;
}
if (pttDown)
{
allowEnqueue = true;
@@ -72,8 +72,8 @@ namespace Barotrauma.Networking
{
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.VOICE);
msg.Write((byte)VoipCapture.Instance.QueueID);
msg.WriteByte((byte)ClientPacketHeader.VOICE);
msg.WriteByte((byte)VoipCapture.Instance.QueueID);
VoipCapture.Instance.Write(msg);
netClient.Send(msg, DeliveryMethod.Unreliable);
@@ -113,35 +113,35 @@ namespace Barotrauma
public void ClientWrite(IWriteMessage msg, VoteType voteType, object data)
{
msg.Write((byte)voteType);
msg.WriteByte((byte)voteType);
switch (voteType)
{
case VoteType.Sub:
if (!(data is SubmarineInfo sub)) { return; }
msg.Write(sub.EqualityCheckVal);
msg.WriteInt32(sub.EqualityCheckVal);
if (sub.EqualityCheckVal == 0)
{
//sub doesn't exist client-side, use hash to let the server know which one we voted for
msg.Write(sub.MD5Hash.StringRepresentation);
msg.WriteString(sub.MD5Hash.StringRepresentation);
}
break;
case VoteType.Mode:
if (!(data is GameModePreset gameMode)) { return; }
msg.Write(gameMode.Identifier);
msg.WriteIdentifier(gameMode.Identifier);
break;
case VoteType.EndRound:
if (!(data is bool)) { return; }
msg.Write((bool)data);
msg.WriteBoolean((bool)data);
break;
case VoteType.Kick:
if (!(data is Client votedClient)) { return; }
msg.Write(votedClient.SessionId);
msg.WriteByte(votedClient.SessionId);
break;
case VoteType.StartRound:
if (!(data is bool)) { return; }
msg.Write((bool)data);
msg.WriteBoolean((bool)data);
break;
case VoteType.PurchaseAndSwitchSub:
case VoteType.PurchaseSub:
@@ -149,22 +149,22 @@ namespace Barotrauma
if (data is (SubmarineInfo voteSub, bool transferItems))
{
//initiate sub vote
msg.Write(true);
msg.Write(voteSub.Name);
msg.Write(transferItems);
msg.WriteBoolean(true);
msg.WriteString(voteSub.Name);
msg.WriteBoolean(transferItems);
}
else
{
// vote
if (!(data is int)) { return; }
msg.Write(false);
msg.Write((int)data);
msg.WriteBoolean(false);
msg.WriteInt32((int)data);
}
break;
case VoteType.TransferMoney:
if (!(data is int)) { return; }
msg.Write(false); //not initiating a vote
msg.Write((int)data);
msg.WriteBoolean(false); //not initiating a vote
msg.WriteInt32((int)data);
break;
}
@@ -1,245 +0,0 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma.Networking
{
partial class WhiteListedPlayer
{
public WhiteListedPlayer(string name, UInt16 identifier, string ip)
{
Name = name;
IP = ip;
UniqueIdentifier = identifier;
}
}
partial class WhiteList
{
private GUIComponent whitelistFrame;
private GUITextBox nameBox;
private GUITextBox ipBox;
private GUIButton addNewButton;
public class LocalAdded
{
public string Name;
public string IP;
};
public bool localEnabled;
public List<UInt16> localRemoved = new List<UInt16>();
public List<LocalAdded> localAdded = new List<LocalAdded>();
public GUIComponent CreateWhiteListFrame(GUIComponent parent)
{
if (whitelistFrame != null)
{
whitelistFrame.Parent.ClearChildren();
whitelistFrame = null;
}
whitelistFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), parent.RectTransform, Anchor.Center))
{
Stretch = true,
RelativeSpacing = 0.02f
};
var enabledTick = new GUITickBox(new RectTransform(new Vector2(0.1f, 0.1f), whitelistFrame.RectTransform), TextManager.Get("WhiteListEnabled"))
{
Selected = localEnabled,
UpdateOrder = 1,
OnSelected = (GUITickBox box) =>
{
nameBox.Enabled = box.Selected;
ipBox.Enabled = box.Selected;
addNewButton.Enabled = box.Selected && !string.IsNullOrEmpty(ipBox.Text) && !string.IsNullOrEmpty(nameBox.Text);
localEnabled = box.Selected;
return true;
}
};
var listBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.7f), whitelistFrame.RectTransform));
foreach (WhiteListedPlayer wlp in whitelistedPlayers)
{
if (localRemoved.Contains(wlp.UniqueIdentifier)) continue;
string blockText = wlp.Name;
if (!string.IsNullOrWhiteSpace(wlp.IP)) blockText += " (" + wlp.IP + ")";
GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), listBox.Content.RectTransform),
blockText)
{
UserData = wlp
};
var removeButton = new GUIButton(new RectTransform(new Vector2(0.3f, 0.8f), textBlock.RectTransform, Anchor.CenterRight),
TextManager.Get("WhiteListRemove"), style: "GUIButtonSmall")
{
UserData = wlp,
OnClicked = RemoveFromWhiteList
};
}
foreach (LocalAdded lad in localAdded)
{
string blockText = lad.Name;
if (!string.IsNullOrWhiteSpace(lad.IP)) blockText += " (" + lad.IP + ")";
GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), listBox.Content.RectTransform),
blockText)
{
UserData = lad
};
var removeButton = new GUIButton(new RectTransform(new Vector2(0.3f, 0.8f), textBlock.RectTransform, Anchor.CenterRight),
TextManager.Get("WhiteListRemove"), style: "GUIButtonSmall")
{
UserData = lad,
OnClicked = RemoveFromWhiteList
};
}
foreach (GUIComponent c in listBox.Content.Children)
{
c.RectTransform.MinSize = new Point(0, Math.Max((int)(20 * GUI.Scale), c.RectTransform.Children.Max(c2 => c2.MinSize.Y)));
}
var nameArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), whitelistFrame.RectTransform), isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.05f
};
new GUITextBlock(new RectTransform(new Vector2(0.3f, 1.0f), nameArea.RectTransform), TextManager.Get("WhiteListName"));
nameBox = new GUITextBox(new RectTransform(new Vector2(0.7f, 1.0f), nameArea.RectTransform), "");
nameBox.OnTextChanged += (textBox, text) =>
{
addNewButton.Enabled = !string.IsNullOrEmpty(ipBox.Text) && !string.IsNullOrEmpty(nameBox.Text);
return true;
};
nameArea.RectTransform.MinSize = new Point(0, nameBox.RectTransform.MinSize.Y);
var ipArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), whitelistFrame.RectTransform), isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.05f
};
new GUITextBlock(new RectTransform(new Vector2(0.3f, 1.0f), ipArea.RectTransform), TextManager.Get("WhiteListIP"));
ipBox = new GUITextBox(new RectTransform(new Vector2(0.7f, 1.0f), ipArea.RectTransform), "");
ipBox.OnTextChanged += (textBox, text) =>
{
addNewButton.Enabled = !string.IsNullOrEmpty(ipBox.Text) && !string.IsNullOrEmpty(nameBox.Text);
return true;
};
ipBox.RectTransform.MinSize = new Point(0, ipBox.RectTransform.MinSize.Y);
addNewButton = new GUIButton(new RectTransform(new Vector2(0.5f, 0.1f), whitelistFrame.RectTransform), TextManager.Get("WhiteListAdd"), style: "GUIButtonSmall")
{
OnClicked = AddToWhiteList
};
GUITextBlock.AutoScaleAndNormalize(addNewButton.TextBlock);
nameBox.Enabled = localEnabled;
ipBox.Enabled = localEnabled;
addNewButton.Enabled = false;
return parent;
}
private bool RemoveFromWhiteList(GUIButton button, object obj)
{
if (obj is WhiteListedPlayer)
{
if (!(obj is WhiteListedPlayer wlp)) return false;
if (!localRemoved.Contains(wlp.UniqueIdentifier)) localRemoved.Add(wlp.UniqueIdentifier);
}
else if (obj is LocalAdded)
{
if (!(obj is LocalAdded lad)) return false;
if (localAdded.Contains(lad)) localAdded.Remove(lad);
}
if (whitelistFrame != null)
{
CreateWhiteListFrame(whitelistFrame.Parent);
}
return true;
}
private bool AddToWhiteList(GUIButton button, object obj)
{
if (string.IsNullOrWhiteSpace(nameBox.Text)) return false;
if (whitelistedPlayers.Any(x => x.Name.ToLower() == nameBox.Text.ToLower() && x.IP == ipBox.Text)) return false;
if (!localAdded.Any(p => p.IP == ipBox.Text)) localAdded.Add(new LocalAdded() { Name = nameBox.Text, IP = ipBox.Text });
if (whitelistFrame != null)
{
CreateWhiteListFrame(whitelistFrame.Parent);
}
return true;
}
public void ClientAdminRead(IReadMessage incMsg)
{
bool hasPermission = incMsg.ReadBoolean();
if (!hasPermission)
{
incMsg.ReadPadBits();
return;
}
bool isOwner = incMsg.ReadBoolean();
localEnabled = incMsg.ReadBoolean();
Enabled = localEnabled;
incMsg.ReadPadBits();
whitelistedPlayers.Clear();
UInt32 bannedPlayerCount = incMsg.ReadVariableUInt32();
for (int i = 0; i < (int)bannedPlayerCount; i++)
{
string name = incMsg.ReadString();
UInt16 uniqueIdentifier = incMsg.ReadUInt16();
string ip = "";
if (isOwner)
{
ip = incMsg.ReadString();
}
else
{
ip = "IP concealed by host";
}
whitelistedPlayers.Add(new WhiteListedPlayer(name, uniqueIdentifier, ip));
}
if (whitelistFrame != null)
{
CreateWhiteListFrame(whitelistFrame.Parent);
}
}
public void ClientAdminWrite(IWriteMessage outMsg)
{
outMsg.Write(localEnabled);
outMsg.WritePadBits();
outMsg.Write((UInt16)localRemoved.Count);
foreach (UInt16 uniqueId in localRemoved)
{
outMsg.Write(uniqueId);
}
outMsg.Write((UInt16)localAdded.Count);
foreach (LocalAdded lad in localAdded)
{
outMsg.Write(lad.Name);
outMsg.Write(lad.IP); //TODO: ENCRYPT
}
localRemoved.Clear();
localAdded.Clear();
}
}
}