From 6a120581485c47c414eed64b4b0eb3037fb0a916 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 19 Mar 2019 18:03:43 +0200 Subject: [PATCH] 8a2a718...633e54b commit 633e54b2ffb4e5ec13c1fa5ce8170f5e726f8e10 Author: Joonas Rikkonen Date: Tue Mar 19 18:02:21 2019 +0200 Include level equality check value in round start messages, so clients immediately know if the level generated at their end doesn't match the one generated by the server (which will cause ID mismatches and more hard-to-diagnose desync kicks during the rounds). Related to #848 commit 68e410705115ece2fcc4ca9c7d9856cc1dd5c1f8 Author: Joonas Rikkonen Date: Tue Mar 19 18:01:01 2019 +0200 Readded client error handling from ef9afed. Not sure how it got removed, probably a messed up merge somewhere down the line when working on the client-server-separation branch or when merging the Steam version work from dev to master. In any case this should help diagnose desync kicks such as #1293. --- .../Source/Networking/GameClient.cs | 17 +++++-- .../ClientEntityEventManager.cs | 6 ++- .../Source/Networking/GameServer.cs | 49 +++++++++++++++++++ 3 files changed, 66 insertions(+), 6 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs index deebab3d5..96fbc7dfe 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs @@ -1029,9 +1029,10 @@ namespace Barotrauma.Networking EndVoteTickBox.Selected = false; - int seed = inc.ReadInt32(); - string levelSeed = inc.ReadString(); - float levelDifficulty = inc.ReadFloat(); + int seed = inc.ReadInt32(); + string levelSeed = inc.ReadString(); + int levelEqualityCheckVal = inc.ReadInt32(); + float levelDifficulty = inc.ReadFloat(); byte losMode = inc.ReadByte(); @@ -1100,6 +1101,14 @@ namespace Barotrauma.Networking mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]); } + if (Level.Loaded.EqualityCheckVal != levelEqualityCheckVal) + { + string errorMsg = " Level equality check failed. The level generated at your end doesn't match the level generated by the server (seed " + Level.Loaded.Seed + ")."; + DebugConsole.ThrowError(errorMsg, createMessageBox: true); + CoroutineManager.StartCoroutine(EndGame("")); + yield return CoroutineStatus.Failure; + } + if (respawnAllowed) respawnManager = new RespawnManager(this, GameMain.NetLobbyScreen.UsingShuttle ? GameMain.NetLobbyScreen.SelectedShuttle : null); gameStarted = true; @@ -1414,7 +1423,7 @@ namespace Barotrauma.Networking break; case ServerNetObject.ENTITY_EVENT: case ServerNetObject.ENTITY_EVENT_INITIAL: - entityEventManager.Read(objHeader, inc, sendingTime, entities); + if (!entityEventManager.Read(objHeader, inc, sendingTime, entities)) { break; } break; case ServerNetObject.CHAT_MESSAGE: ChatMessage.ClientRead(inc); diff --git a/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs b/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs index a17d20aac..f7451440a 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs @@ -114,9 +114,9 @@ namespace Barotrauma.Networking private UInt16? firstNewID; /// - /// Read the events from the message, ignoring ones we've already received + /// Read the events from the message, ignoring ones we've already received. Returns false if reading the events fails. /// - public void Read(ServerNetObject type, NetIncomingMessage msg, float sendingTime, List entities) + public bool Read(ServerNetObject type, NetIncomingMessage msg, float sendingTime, List entities) { UInt16 unreceivedEntityEventCount = 0; @@ -192,6 +192,7 @@ namespace Barotrauma.Networking "Received msg " + thisEventID + ", entity " + entityID + " not found", Microsoft.Xna.Framework.Color.Red); GameMain.Client.ReportError(ClientNetError.MISSING_ENTITY, eventID: thisEventID, entityID: entityID); + return false; } msg.Position += msgLength * 8; @@ -230,6 +231,7 @@ namespace Barotrauma.Networking } msg.ReadPadBits(); } + return true; } protected override void WriteEvent(NetBuffer buffer, NetEntityEvent entityEvent, Client recipient = null) diff --git a/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs index 4c4641540..0bf1f2535 100644 --- a/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs @@ -734,9 +734,57 @@ namespace Barotrauma.Networking fileSender.ReadFileRequest(inc, connectedClient); } break; + case ClientPacketHeader.ERROR: + HandleClientError(inc, connectedClient); + break; } } + private void HandleClientError(NetIncomingMessage inc, Client c) + { + string errorStr = "Unhandled error report"; + + ClientNetError error = (ClientNetError)inc.ReadByte(); + int levelEqualityCheckVal = inc.ReadInt32(); + switch (error) + { + case ClientNetError.MISSING_EVENT: + UInt16 expectedID = inc.ReadUInt16(); + UInt16 receivedID = inc.ReadUInt16(); + errorStr = "Expecting event id " + expectedID.ToString() + ", received " + receivedID.ToString(); + break; + case ClientNetError.MISSING_ENTITY: + UInt16 eventID = inc.ReadUInt16(); + UInt16 entityID = inc.ReadUInt16(); + Entity entity = Entity.FindEntityByID(entityID); + if (entity == null) + { + errorStr = "Received an update for an entity that doesn't exist (event id " + eventID.ToString() + ", entity id " + entityID.ToString() + ")."; + } + else if (entity is Character character) + { + errorStr = "Missing character " + character.Name + " (event id " + eventID.ToString() + ", entity id " + entityID.ToString() + ")."; + } + else if (entity is Item item) + { + errorStr = "Missing item " + item.Name + " (event id " + eventID.ToString() + ", entity id " + entityID.ToString() + ")."; + } + else + { + errorStr = "Missing entity " + entity.ToString() + " (event id " + eventID.ToString() + ", entity id " + entityID.ToString() + ")."; + } + break; + } + + if (Level.Loaded != null && levelEqualityCheckVal != Level.Loaded.EqualityCheckVal) + { + errorStr += " Level equality check failed. The level generated at your end doesn't match the level generated by the server (seed " + Level.Loaded.Seed + ")."; + } + + Log(c.Name + " has reported an error: " + errorStr, ServerLog.MessageType.Error); + KickClient(c, errorStr); + } + public override void CreateEntityEvent(INetSerializable entity, object[] extraData = null) { if (!(entity is IServerSerializable)) throw new InvalidCastException("entity is not IServerSerializable"); @@ -1759,6 +1807,7 @@ namespace Barotrauma.Networking msg.Write(seed); msg.Write(GameMain.GameSession.Level.Seed); + msg.Write(GameMain.GameSession.Level.EqualityCheckVal); msg.Write(serverSettings.SelectedLevelDifficulty); msg.Write((byte)GameMain.Config.LosMode);