diff --git a/QSB/WorldSync/HashErrorAnalysis.cs b/QSB/WorldSync/HashErrorAnalysis.cs new file mode 100644 index 000000000..5dea244b8 --- /dev/null +++ b/QSB/WorldSync/HashErrorAnalysis.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using System.Linq; +using OWML.Common; +using QSB.Utility; +using QSB.Player.Messages; +using QSB.Messaging; +using QSB.Utility.Deterministic; + +namespace QSB.WorldSync; + +public class HashErrorAnalysis +{ + public static Dictionary Instances = new(); + + private readonly string _managerName; + + private readonly List<(string hash, string path)> _paths = new(); + + public HashErrorAnalysis(string managerName) => _managerName = managerName; + + public void OnReceiveMessage(string deterministicPath) => _paths.Add((deterministicPath.GetMD5Hash(), deterministicPath)); + + public void AllDataSent(uint from) + { + var serverObjects = QSBWorldSync.GetWorldObjectsFromManager(_managerName); + + var serverDetPaths = serverObjects.Select(x => x.AttachedObject.DeterministicPath()); + var serverDetPathDict = serverDetPaths.Select(path => (path.GetMD5Hash(), path)).ToList<(string hash, string path)>(); + + var serverDoesNotHave = new List(); + var clientDoesNotHave = new List(); + + foreach (var (hash, path) in serverDetPathDict) + { + if (!_paths.Any(x => x.hash == hash)) + { + // client does not contain something from the server + clientDoesNotHave.Add(path); + } + } + + foreach (var (hash, path) in _paths) + { + if (!serverDetPathDict.Any(x => x.hash == hash)) + { + // client does not contain something from the server + serverDoesNotHave.Add(path); + } + } + + DebugLog.ToConsole($"{_managerName} - Client is missing :", MessageType.Error); + foreach (var item in clientDoesNotHave) + { + DebugLog.ToConsole($"- {item}", MessageType.Error); + } + + DebugLog.ToConsole($"{_managerName} - Client has extra :", MessageType.Error); + foreach (var item in serverDoesNotHave) + { + DebugLog.ToConsole($"- {item}", MessageType.Error); + } + Instances.Remove(_managerName); + + new PlayerKickMessage(from, $"WorldObject hash error for {_managerName}").Send(); + } +} diff --git a/QSB/WorldSync/Messages/DataDumpFinishedMessage.cs b/QSB/WorldSync/Messages/DataDumpFinishedMessage.cs new file mode 100644 index 000000000..b8edc5ef9 --- /dev/null +++ b/QSB/WorldSync/Messages/DataDumpFinishedMessage.cs @@ -0,0 +1,10 @@ +using QSB.Messaging; + +namespace QSB.WorldSync.Messages; + +public class DataDumpFinishedMessage : QSBMessage +{ + public DataDumpFinishedMessage(string managerName) : base(managerName) => To = 0; + + public override void OnReceiveRemote() => HashErrorAnalysis.Instances[Data].AllDataSent(From); +} diff --git a/QSB/WorldSync/Messages/HashCheckSucceededMessage.cs b/QSB/WorldSync/Messages/HashCheckSucceededMessage.cs new file mode 100644 index 000000000..c96b49771 --- /dev/null +++ b/QSB/WorldSync/Messages/HashCheckSucceededMessage.cs @@ -0,0 +1,8 @@ +using QSB.Messaging; + +namespace QSB.WorldSync.Messages; + +internal class HashCheckSucceededMessage : QSBMessage +{ + +} \ No newline at end of file diff --git a/QSB/WorldSync/Messages/RequestHashBreakdownMessage.cs b/QSB/WorldSync/Messages/RequestHashBreakdownMessage.cs new file mode 100644 index 000000000..f10fbef59 --- /dev/null +++ b/QSB/WorldSync/Messages/RequestHashBreakdownMessage.cs @@ -0,0 +1,27 @@ +using OWML.Common; +using QSB.Messaging; +using QSB.Utility; + +namespace QSB.WorldSync.Messages; + +/// +/// Sent to clients from the server when a client has an incorrect WorldObject hash. +/// +internal class RequestHashBreakdownMessage : QSBMessage +{ + public RequestHashBreakdownMessage(string managerName) : base(managerName) { } + + public override void OnReceiveRemote() + { + DebugLog.ToConsole($"Received RequestHashBreakdownMessage for {Data}", MessageType.Error); + var objects = QSBWorldSync.GetWorldObjectsFromManager(Data); + + foreach (var worldObject in objects) + { + new WorldObjectInfoMessage(worldObject, Data).Send(); + } + + DebugLog.ToConsole("- Sending finished message.", MessageType.Error); + new DataDumpFinishedMessage(Data).Send(); + } +} diff --git a/QSB/WorldSync/Messages/WorldObjectInfoMessage.cs b/QSB/WorldSync/Messages/WorldObjectInfoMessage.cs new file mode 100644 index 000000000..dc79916ba --- /dev/null +++ b/QSB/WorldSync/Messages/WorldObjectInfoMessage.cs @@ -0,0 +1,17 @@ +using QSB.Messaging; +using QSB.Utility.Deterministic; + +namespace QSB.WorldSync.Messages; + +/// +/// Sent by clients to the server after receiving a RequestHashBreakdown message. +/// +public class WorldObjectInfoMessage : QSBMessage<(string fullPath, string managerName)> +{ + public WorldObjectInfoMessage(IWorldObject obj, string managerName) : base((obj.AttachedObject.DeterministicPath(), managerName)) => To = 0; + + public override void OnReceiveRemote() + { + HashErrorAnalysis.Instances[Data.managerName].OnReceiveMessage(Data.fullPath); + } +} diff --git a/QSB/WorldSync/Messages/WorldObjectsHashMessage.cs b/QSB/WorldSync/Messages/WorldObjectsHashMessage.cs index e638b60aa..d0676888d 100644 --- a/QSB/WorldSync/Messages/WorldObjectsHashMessage.cs +++ b/QSB/WorldSync/Messages/WorldObjectsHashMessage.cs @@ -21,8 +21,14 @@ public override void OnReceiveRemote() if (hash != Data.hash) { // oh fuck oh no oh god - DebugLog.ToConsole($"Kicking {From} because their WorldObjects hash for {Data.managerName} is wrong. (Server:{hash} count:{count}, Client:{Data.hash} count:{Data.count})", MessageType.Error); - new PlayerKickMessage(From, $"WorldObject hash error for {Data.managerName}. (Server:{hash} count:{count}, Client:{Data.hash}, count:{Data.count})").Send(); + /*DebugLog.ToConsole($"Kicking {From} because their WorldObjects hash for {Data.managerName} is wrong. (Server:{hash} count:{count}, Client:{Data.hash} count:{Data.count})", MessageType.Error); + new PlayerKickMessage(From, $"WorldObject hash error for {Data.managerName}. (Server:{hash} count:{count}, Client:{Data.hash}, count:{Data.count})").Send();*/ + + DebugLog.ToConsole($"{From} has an incorrect hash for {Data.managerName}. (S:{hash}:{count}, C:{Data.hash}-{Data.count}) Requesting data for analysis...", MessageType.Error); + + HashErrorAnalysis.Instances.Add(Data.managerName, new HashErrorAnalysis(Data.managerName)); + + new RequestHashBreakdownMessage(Data.managerName) {To = From}.Send(); } }); }