/// <summary> /// Create and show mod compatibility error message /// </summary> private static void ShowModCompatibilityErrorMessage() { var panel = GUIManager.Instance.CreateWoodpanel(GUIManager.PixelFix.transform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 0f), 700, 500); panel.SetActive(true); var remote = new ModuleVersionData(lastServerVersion); var local = new ModuleVersionData(GetEnforcableMods().ToList()); var scroll = GUIManager.Instance.CreateScrollView(panel.transform, false, true, 8f, 10f, GUIManager.Instance.ValheimScrollbarHandleColorBlock, new Color(0.1568628f, 0.1019608f, 0.0627451f, 1f), 650f, 400f); scroll.SetActive(true); GUIManager.Instance.CreateText("Remote version:", scroll.transform.Find("Scroll View/Viewport/Content"), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0, 0), GUIManager.Instance.AveriaSerifBold, 19, GUIManager.Instance.ValheimOrange, true, new Color(0, 0, 0, 1), 600f, 40f, false); GUIManager.Instance.CreateText(remote.ToString(false), scroll.transform.Find("Scroll View/Viewport/Content"), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0, 0), GUIManager.Instance.AveriaSerifBold, 19, Color.white, true, new Color(0, 0, 0, 1), 600f, 40f, false); GUIManager.Instance.CreateText("Local version:", scroll.transform.Find("Scroll View/Viewport/Content"), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0, 0), GUIManager.Instance.AveriaSerifBold, 19, GUIManager.Instance.ValheimOrange, true, new Color(0, 0, 0, 1), 600f, 40f, false); GUIManager.Instance.CreateText(local.ToString(false), scroll.transform.Find("Scroll View/Viewport/Content"), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0, 0), GUIManager.Instance.AveriaSerifBold, 19, Color.white, true, new Color(0, 0, 0, 1), 600f, 40f, false); foreach (var part in CreateErrorMessage(remote, local)) { GUIManager.Instance.CreateText(part.Item2, scroll.transform.Find("Scroll View/Viewport/Content"), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0, 0), GUIManager.Instance.AveriaSerifBold, 19, part.Item1, true, new Color(0, 0, 0, 1), 600f, 40f, false); } scroll.transform.Find("Scroll View").GetComponent <ScrollRect>().verticalNormalizedPosition = 1f; var button = GUIManager.Instance.CreateButton("OK", panel.transform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, -215f)); // Special condition, coming from ingame back into main scene button.GetComponent <Image>().pixelsPerUnitMultiplier = 2f; button.SetActive(true); button.GetComponent <Button>().onClick.AddListener(() => { panel.SetActive(false); Object.Destroy(panel); }); // Reset the last server version lastServerVersion = null; }
// Hook RPC_PeerInfo to check in front of the original method private static void ZNet_RPC_PeerInfo(On.ZNet.orig_RPC_PeerInfo orig, ZNet self, ZRpc rpc, ZPackage pkg) { if (ZNet.instance.IsServerInstance() || ZNet.instance.IsLocalInstance()) { try { var clientVersion = new ModuleVersionData(clientVersions[rpc.GetSocket().GetEndPointString()]); var serverVersion = new ModuleVersionData(GetEnforcableMods().ToList()); // Remove from list clientVersions.Remove(rpc.GetSocket().GetEndPointString()); // Compare and disconnect when not equal if (!clientVersion.Equals(serverVersion)) { rpc.Invoke("Error", 3); return; } } catch (EndOfStreamException) { Logger.LogError("Reading beyond end of stream. Probably client without Jotunn tried to connect."); // Client did not send appended package, just disconnect with the incompatible version error rpc.Invoke("Error", 3); return; } catch (KeyNotFoundException ex) { // Vanilla client trying to connect? // Check mods, if there are some installed on the server which need also to be on the client if (GetEnforcableMods().Any(x => x.Item3 == CompatibilityLevel.EveryoneMustHaveMod)) { // There is a mod, which needs to be client side too // Lets disconnect the vanilla client with Incompatible Version message rpc.Invoke("Error", 3); return; } } } else { // If we got this far on client side, clear lastServerVersion again lastServerVersion = null; } // call original method orig(self, rpc, pkg); }
/// <summary> /// Store server's message. /// </summary> /// <param name="sender"></param> /// <param name="data"></param> private static void RPC_Jotunn_ReceiveVersionData(ZRpc sender, ZPackage data) { Logger.LogDebug($"Received Version package from {sender.m_socket.GetEndPointString()}"); if (!ZNet.instance.IsClientInstance()) { clientVersions[sender.m_socket.GetEndPointString()] = data; var clientVersion = new ModuleVersionData(clientVersions[sender.GetSocket().GetEndPointString()]); var serverVersion = new ModuleVersionData(GetEnforcableMods().ToList()); if (!clientVersion.Equals(serverVersion)) { // Disconnect if mods are not network compatible sender.Invoke("Error", 3); } } else { lastServerVersion = data; } }
/// <summary> /// Store server's message. /// </summary> /// <param name="sender"></param> /// <param name="data"></param> private static void RPC_Jotunn_ReceiveVersionData(ZRpc sender, ZPackage data) { Logger.LogDebug($"Received Version package from {sender.m_socket.GetEndPointString()}"); if (!ZNet.instance.IsClientInstance()) { ClientVersions[sender.m_socket.GetEndPointString()] = data; var serverData = new ModuleVersionData(GetEnforcableMods().ToList()); var clientData = new ModuleVersionData(data); if (!CompareVersionData(serverData, clientData)) { // Disconnect if mods are not network compatible Logger.LogWarning("RPC_Jotunn_ReceiveVersionData: Disconnecting modded client with incompatible version message. " + "Mods are not compatible"); sender.Invoke("Error", (int)ZNet.ConnectionStatus.ErrorVersion); } } else { LastServerVersion = data; } }
private static bool ZNet_RPC_PeerInfo(ZNet __instance, ZRpc rpc, ZPackage pkg) { if (!ZNet.instance.IsClientInstance()) { // Vanilla client trying to connect? if (!ClientVersions.ContainsKey(rpc.GetSocket().GetEndPointString())) { // Check mods, if there are some installed on the server which need also to be on the client if (GetEnforcableMods().Any(x => x.Item3 == CompatibilityLevel.EveryoneMustHaveMod || x.Item3 == CompatibilityLevel.ClientMustHaveMod)) { // There is a mod, which needs to be client side too // Lets disconnect the vanilla client with Incompatible Version message Logger.LogWarning("Disconnecting vanilla client with incompatible version message. " + "There are mods that need to be installed on the client"); rpc.Invoke("Error", (int)ZNet.ConnectionStatus.ErrorVersion); return(false); } } else { var serverData = new ModuleVersionData(GetEnforcableMods().ToList()); var clientData = new ModuleVersionData(ClientVersions[rpc.m_socket.GetEndPointString()]); if (!CompareVersionData(serverData, clientData)) { // Disconnect if mods are not network compatible Logger.LogWarning("RPC_PeerInfo: Disconnecting modded client with incompatible version message. " + "Mods are not compatible"); rpc.Invoke("Error", (int)ZNet.ConnectionStatus.ErrorVersion); return(false); } } } return(true); }
/// <summary> /// Create the error message(s) from the server and client message data /// </summary> /// <param name="server">server data</param> /// <param name="client">client data</param> /// <returns></returns> private static IEnumerable <Tuple <Color, string> > CreateErrorMessage(ModuleVersionData server, ModuleVersionData client) { // Check Valheim version first if (server.ValheimVersion != client.ValheimVersion) { yield return(new Tuple <Color, string>(Color.red, "Valheim version error:")); if (server.ValheimVersion > client.ValheimVersion) { yield return(new Tuple <Color, string>(Color.white, $"Please update your client to version {server.ValheimVersion}")); } if (server.ValheimVersion < client.ValheimVersion) { yield return(new Tuple <Color, string>(Color.white, $"The server you tried to connect runs {server.ValheimVersion}, which is lower than your version ({client.ValheimVersion})")); yield return(new Tuple <Color, string>(Color.white, "Please contact the server admin for a server update." + Environment.NewLine)); } } // And then each module foreach (var module in server.Modules) { // Check first for missing modules on the client side if (module.Item3 == CompatibilityLevel.EveryoneMustHaveMod) { if (client.Modules.All(x => x.Item1 != module.Item1)) { // client is missing needed module yield return(new Tuple <Color, string>(Color.red, "Missing mod:")); yield return(new Tuple <Color, string>(Color.white, $"Please install mod {module.Item1} v{module.Item2}" + Environment.NewLine)); continue; } } // Then all version checks var clientModule = client.Modules.First(x => x.Item1 == module.Item1); // Major if (module.Item4 >= VersionStrictness.Major || clientModule.Item4 >= VersionStrictness.Major) { if (module.Item2.Major > clientModule.Item2.Major) { foreach (var messageLine in ClientVersionLowerMessage(module)) { yield return(messageLine); } continue; } if (module.Item2.Major < clientModule.Item2.Major) { foreach (var messageLine in ServerVersionLowerMessage(module, clientModule)) { yield return(messageLine); } continue; } // Minor if (module.Item4 >= VersionStrictness.Minor || clientModule.Item4 >= VersionStrictness.Minor) { if (module.Item2.Minor > clientModule.Item2.Minor) { foreach (var messageLine in ClientVersionLowerMessage(module)) { yield return(messageLine); } continue; } if (module.Item2.Minor < clientModule.Item2.Minor) { foreach (var messageLine in ServerVersionLowerMessage(module, clientModule)) { yield return(messageLine); } continue; } } // Patch if (module.Item4 >= VersionStrictness.Patch || clientModule.Item4 >= VersionStrictness.Patch) { if (module.Item2.Build > clientModule.Item2.Build) { foreach (var messageLine in ClientVersionLowerMessage(module)) { yield return(messageLine); } continue; } if (module.Item2.Build < clientModule.Item2.Build) { foreach (var messageLine in ServerVersionLowerMessage(module, clientModule)) { yield return(messageLine); } } } } } // Now lets find additional modules with NetworkCompatibility attribute in the client's list foreach (var module in client.Modules.Where(x => x.Item3 == CompatibilityLevel.EveryoneMustHaveMod)) { if (server.Modules.All(x => x.Item1 != module.Item1)) { yield return(new Tuple <Color, string>(Color.red, "Additional mod detected:")); yield return(new Tuple <Color, string>(GUIManager.Instance.ValheimOrange, $"Mod {module.Item1} v{module.Item2} is not installed on the server.")); yield return(new Tuple <Color, string>(Color.white, "Please consider uninstalling this mod." + Environment.NewLine)); } } }
/// <summary> /// Create the error message(s) from the server and client message data /// </summary> /// <param name="serverData">server data</param> /// <param name="clientData">client data</param> /// <returns></returns> private static IEnumerable <Tuple <Color, string> > CreateErrorMessage(ModuleVersionData serverData, ModuleVersionData clientData) { // Check Valheim version first if (serverData.ValheimVersion != clientData.ValheimVersion) { yield return(new Tuple <Color, string>(Color.red, "Valheim version error:")); if (serverData.ValheimVersion > clientData.ValheimVersion) { yield return(new Tuple <Color, string>(Color.white, $"Please update your client to version {serverData.ValheimVersion}")); } if (serverData.ValheimVersion < clientData.ValheimVersion) { yield return(new Tuple <Color, string>(Color.white, $"The server you tried to connect runs {serverData.ValheimVersion}, which is lower than your version ({clientData.ValheimVersion})")); yield return(new Tuple <Color, string>(Color.white, "Please contact the server admin for a server update." + Environment.NewLine)); } } // And then each module foreach (var serverModule in serverData.Modules) { // Check first for missing modules on the client side if (serverModule.Item3 == CompatibilityLevel.EveryoneMustHaveMod || serverModule.Item3 == CompatibilityLevel.ClientMustHaveMod) { if (clientData.Modules.All(x => x.Item1 != serverModule.Item1)) { // client is missing needed module yield return(new Tuple <Color, string>(Color.red, "Missing mod:")); yield return(new Tuple <Color, string>(Color.white, $"Please install mod {serverModule.Item1} v{serverModule.Item2}" + Environment.NewLine)); continue; } if (!clientData.Modules.Any(x => x.Item1 == serverModule.Item1 && x.Item3 == serverModule.Item3)) { // module is there but mod compat level is lower yield return(new Tuple <Color, string>(Color.red, "Compatibility level mismatch:")); yield return(new Tuple <Color, string>(Color.white, $"Please update mod {serverModule.Item1} version v{serverModule.Item2}." + Environment.NewLine)); continue; } } // Then all version checks var clientModule = clientData.Modules.FirstOrDefault(x => x.Item1 == serverModule.Item1); #pragma warning disable CS0618 // Type or member is obsolete if (clientModule == null && (serverModule.Item3 == CompatibilityLevel.NotEnforced || serverModule.Item3 == CompatibilityLevel.NoNeedForSync)) { continue; } #pragma warning restore CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete if (clientModule == null && (serverModule.Item3 == CompatibilityLevel.OnlySyncWhenInstalled || serverModule.Item3 == CompatibilityLevel.VersionCheckOnly || serverModule.Item3 == CompatibilityLevel.ServerMustHaveMod)) { continue; } #pragma warning restore CS0618 // Type or member is obsolete // Major if (serverModule.Item4 >= VersionStrictness.Major || clientModule.Item4 >= VersionStrictness.Major) { if (serverModule.Item2.Major > clientModule.Item2.Major) { foreach (var messageLine in ClientVersionLowerMessage(serverModule)) { yield return(messageLine); } continue; } if (serverModule.Item2.Major < clientModule.Item2.Major) { foreach (var messageLine in ServerVersionLowerMessage(serverModule, clientModule)) { yield return(messageLine); } continue; } // Minor if (serverModule.Item4 >= VersionStrictness.Minor || clientModule.Item4 >= VersionStrictness.Minor) { if (serverModule.Item2.Minor > clientModule.Item2.Minor) { foreach (var messageLine in ClientVersionLowerMessage(serverModule)) { yield return(messageLine); } continue; } if (serverModule.Item2.Minor < clientModule.Item2.Minor) { foreach (var messageLine in ServerVersionLowerMessage(serverModule, clientModule)) { yield return(messageLine); } continue; } } // Patch if (serverModule.Item4 >= VersionStrictness.Patch || clientModule.Item4 >= VersionStrictness.Patch) { if (serverModule.Item2.Build > clientModule.Item2.Build) { foreach (var messageLine in ClientVersionLowerMessage(serverModule)) { yield return(messageLine); } continue; } if (serverModule.Item2.Build < clientModule.Item2.Build) { foreach (var messageLine in ServerVersionLowerMessage(serverModule, clientModule)) { yield return(messageLine); } } } } } // Now lets find additional modules with NetworkCompatibility attribute in the client's list foreach (var clientModule in clientData.Modules.Where(x => x.Item3 == CompatibilityLevel.EveryoneMustHaveMod || x.Item3 == CompatibilityLevel.ServerMustHaveMod)) { if (serverData.Modules.All(x => x.Item1 != clientModule.Item1)) { yield return(new Tuple <Color, string>(Color.red, "Additional mod detected:")); yield return(new Tuple <Color, string>(GUIManager.Instance.ValheimOrange, $"Mod {clientModule.Item1} v{clientModule.Item2} is not installed on the server.")); yield return(new Tuple <Color, string>(Color.white, "Please consider uninstalling this mod." + Environment.NewLine)); continue; } if (!serverData.Modules.Any(x => x.Item1 == clientModule.Item1 && x.Item3 == clientModule.Item3)) { yield return(new Tuple <Color, string>(Color.red, "Compatibility level mismatch:")); yield return(new Tuple <Color, string>(Color.white, $"Please update mod {clientModule.Item1} version v{clientModule.Item2} on the server." + Environment.NewLine)); continue; } } }
internal static bool CompareVersionData(ModuleVersionData serverData, ModuleVersionData clientData) { if (ReferenceEquals(serverData, clientData)) { return(true); } if (!Equals(serverData.ValheimVersion, clientData.ValheimVersion)) { return(false); } // Check server enforced mods foreach (var serverModule in serverData.Modules.Where(x => x.Item3 == CompatibilityLevel.EveryoneMustHaveMod || x.Item3 == CompatibilityLevel.ClientMustHaveMod)) { if (!clientData.Modules.Any(x => x.Item1 == serverModule.Item1 && x.Item3 == serverModule.Item3)) { return(false); } } // Check client enforced mods foreach (var clientModule in clientData.Modules.Where(x => x.Item3 == CompatibilityLevel.EveryoneMustHaveMod || x.Item3 == CompatibilityLevel.ServerMustHaveMod)) { if (!serverData.Modules.Any(x => x.Item1 == clientModule.Item1 && x.Item3 == clientModule.Item3)) { return(false); } } // Compare modules foreach (var serverModule in serverData.Modules) { #pragma warning disable CS0618 // Type or member is obsolete if (serverModule.Item3 == CompatibilityLevel.NoNeedForSync || serverModule.Item3 == CompatibilityLevel.NotEnforced) { continue; } #pragma warning restore CS0618 // Type or member is obsolete var clientModule = clientData.Modules.FirstOrDefault(x => x.Item1 == serverModule.Item1); #pragma warning disable CS0618 // Type or member is obsolete if (clientModule == null && (serverModule.Item3 == CompatibilityLevel.OnlySyncWhenInstalled || serverModule.Item3 == CompatibilityLevel.VersionCheckOnly || serverModule.Item3 == CompatibilityLevel.ServerMustHaveMod)) { continue; } #pragma warning restore CS0618 // Type or member is obsolete if (clientModule == null) { return(false); } if (serverModule.Item2.Major != clientModule.Item2.Major && (serverModule.Item4 >= VersionStrictness.Major || clientModule.Item4 >= VersionStrictness.Major)) { return(false); } if (serverModule.Item2.Minor != clientModule.Item2.Minor && (serverModule.Item4 >= VersionStrictness.Minor || clientModule.Item4 >= VersionStrictness.Minor)) { return(false); } if (serverModule.Item2.Build != clientModule.Item2.Build && (serverModule.Item4 >= VersionStrictness.Patch || clientModule.Item4 >= VersionStrictness.Patch)) { return(false); } } return(true); }