Decompiled source of Longhouse Core v1.0.1

plugins/Core/EzomicCore.dll

Decompiled 11 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[assembly: AssemblyCompany("Thijssen Software")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright (c) 2026 Robbin Thijssen")]
[assembly: AssemblyDescription("Shared plumbing for the Ezomic mods: version gating, host-authoritative config, and the shared inventory height.")]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: AssemblyInformationalVersion("1.0.1+4005a9f7247f5f17138cd2299f2bdeb790cf3d2b")]
[assembly: AssemblyProduct("Core")]
[assembly: AssemblyTitle("EzomicCore")]
[assembly: AssemblyVersion("1.0.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace Ezomic.Core
{
	internal static class ConfigSync
	{
		private static readonly Dictionary<ConfigEntryBase, object> Original = new Dictionary<ConfigEntryBase, object>();

		private static readonly Dictionary<ConfigEntryBase, object> Imposed = new Dictionary<ConfigEntryBase, object>();

		private static readonly HashSet<ConfigFile> Watched = new HashSet<ConfigFile>();

		private static bool _applying;

		internal static ZPackage BuildConfig()
		{
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Expected O, but got Unknown
			List<string[]> list = new List<string[]>();
			foreach (KeyValuePair<string, ModEntry> mod in Suite.Mods)
			{
				Suite.AbsorbConfig(mod.Value);
				foreach (KeyValuePair<string, ConfigEntryBase> item in mod.Value.Synced)
				{
					string text;
					try
					{
						text = TomlTypeConverter.ConvertToString(item.Value.BoxedValue, item.Value.SettingType);
					}
					catch (Exception ex)
					{
						CorePlugin.Log.LogWarning((object)("Cannot send " + mod.Key + " " + item.Key + ": " + ex.Message));
						continue;
					}
					list.Add(new string[3] { mod.Key, item.Key, text });
				}
			}
			ZPackage val = new ZPackage();
			val.Write(list.Count);
			foreach (string[] item2 in list)
			{
				val.Write(item2[0]);
				val.Write(item2[1]);
				val.Write(item2[2]);
			}
			return val;
		}

		internal static void ReceiveConfig(ZRpc rpc, ZPackage pkg)
		{
			if (((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) || !CorePlugin.EnforceConfig.Value)
			{
				return;
			}
			int num = pkg.ReadInt();
			int num2 = 0;
			_applying = true;
			try
			{
				for (int i = 0; i < num; i++)
				{
					string guid = pkg.ReadString();
					string key = pkg.ReadString();
					string raw = pkg.ReadString();
					if (Apply(guid, key, raw))
					{
						num2++;
					}
				}
			}
			finally
			{
				_applying = false;
			}
			CorePlugin.Log.LogInfo((object)("Host settings applied: " + num2 + " of " + num + ". Your own config file is untouched and comes back on disconnect."));
		}

		private static bool Apply(string guid, string key, string raw)
		{
			if (!Suite.Mods.TryGetValue(guid, out var value))
			{
				return false;
			}
			if (!value.Synced.TryGetValue(key, out var value2))
			{
				return false;
			}
			object obj;
			try
			{
				obj = TomlTypeConverter.ConvertToValue(raw, value2.SettingType);
			}
			catch (Exception ex)
			{
				CorePlugin.Log.LogWarning((object)("Cannot read host value for " + key + ": " + ex.Message));
				return false;
			}
			if (!Original.ContainsKey(value2))
			{
				Original[value2] = value2.BoxedValue;
			}
			Imposed[value2] = obj;
			value2.BoxedValue = obj;
			SetReadOnly(value2, readOnly: true);
			Watch(value.Config);
			return true;
		}

		private static void Watch(ConfigFile config)
		{
			if (config == null || !Watched.Add(config))
			{
				return;
			}
			config.SettingChanged += delegate(object sender, SettingChangedEventArgs args)
			{
				if (_applying || !Imposed.TryGetValue(args.ChangedSetting, out var value))
				{
					return;
				}
				_applying = true;
				try
				{
					args.ChangedSetting.BoxedValue = value;
				}
				finally
				{
					_applying = false;
				}
			};
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(ZNet), "Shutdown")]
		private static void RestoreOnShutdown()
		{
			if (Original.Count == 0)
			{
				return;
			}
			_applying = true;
			try
			{
				foreach (KeyValuePair<ConfigEntryBase, object> item in Original)
				{
					item.Key.BoxedValue = item.Value;
					SetReadOnly(item.Key, readOnly: false);
				}
			}
			finally
			{
				_applying = false;
			}
			CorePlugin.Log.LogInfo((object)("Restored " + Original.Count + " of your own settings."));
			Original.Clear();
			Imposed.Clear();
		}

		private static void SetReadOnly(ConfigEntryBase entry, bool readOnly)
		{
			if (entry.Description == null || entry.Description.Tags == null)
			{
				return;
			}
			object[] tags = entry.Description.Tags;
			for (int i = 0; i < tags.Length; i++)
			{
				if (tags[i] is ConfigurationManagerAttributes configurationManagerAttributes)
				{
					configurationManagerAttributes.ReadOnly = readOnly;
				}
			}
		}
	}
	internal static class ConnectError
	{
		private static string _pending;

		internal static void Expect(string reason)
		{
			if (!string.IsNullOrEmpty(reason))
			{
				_pending = reason;
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(FejdStartup), "ShowConnectError")]
		private static void Explain(FejdStartup __instance)
		{
			if (string.IsNullOrEmpty(_pending))
			{
				return;
			}
			string pending = _pending;
			_pending = null;
			FieldInfo fieldInfo = AccessTools.Field(typeof(FejdStartup), "m_connectionFailedError");
			object obj = ((fieldInfo != null) ? fieldInfo.GetValue(__instance) : null);
			if (obj != null)
			{
				PropertyInfo propertyInfo = AccessTools.Property(obj.GetType(), "text");
				if (!(propertyInfo == null))
				{
					string text = propertyInfo.GetValue(obj, null) as string;
					propertyInfo.SetValue(obj, string.IsNullOrEmpty(text) ? pending : (text + "\n\n" + pending), null);
				}
			}
		}
	}
	[BepInPlugin("ezomic.valheim.core", "Core", "1.0.1")]
	public class CorePlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "ezomic.valheim.core";

		public const string PluginName = "Core";

		public const string PluginVersion = "1.0.1";

		public const string PluginAuthor = "Robbin Thijssen";

		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> EnforceVersions;

		internal static ConfigEntry<bool> EnforceBuilds;

		internal static ConfigEntry<bool> EnforceConfig;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			EnforceVersions = ((BaseUnityPlugin)this).Config.Bind<bool>("Multiplayer", "EnforceVersions", true, "Refuse a connection when the client and the server disagree about which Ezomic mods are installed, or about their versions. Turning this off does not make a mismatch safe; it makes it silent.");
			EnforceBuilds = ((BaseUnityPlugin)this).Config.Bind<bool>("Multiplayer", "EnforceBuilds", true, "Also refuse a connection when both ends claim the same version but are actually different builds.\nA version string is whatever was last remembered to be edited, and during development every build says 0.1.0 - so a client three commits ahead of the server matches perfectly and connects. That is the mismatch that actually happens, and a version check is the least able to see it. This compares the compiler's build id instead, which no one has to remember.\nTurn it off if you build the mods yourself on more than one machine: deterministic builds also depend on source paths, so the same commit checked out to a different folder produces a different id.");
			EnforceConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Multiplayer", "EnforceConfig", true, "The host's settings win. Clients keep their own file untouched and get it back the moment they disconnect - nothing is overwritten on disk.");
			Suite.Register("ezomic.valheim.core", "Core", "1.0.1", ((BaseUnityPlugin)this).Config, Requirement.Everyone, typeof(CorePlugin).Assembly);
			_harmony = new Harmony("ezomic.valheim.core");
			_harmony.PatchAll(typeof(NetworkPatches));
			_harmony.PatchAll(typeof(ConfigSync));
			_harmony.PatchAll(typeof(ConnectError));
			_harmony.PatchAll(typeof(InventoryRows));
			Log.LogInfo((object)"Core 1.0.1 by Robbin Thijssen - ready.");
		}

		private void Update()
		{
			InventoryRows.Tick();
			InventoryRows.Backdrop.Tick();
		}

		private void OnDestroy()
		{
			if (_harmony != null)
			{
				_harmony.UnpatchSelf();
			}
		}
	}
	public enum Requirement
	{
		Everyone,
		HostOnly
	}
	internal sealed class ModEntry
	{
		internal string Guid;

		internal string Name;

		internal string Version;

		internal Requirement Requirement;

		internal ConfigFile Config;

		internal string Fingerprint;

		internal string Data;

		internal readonly Dictionary<string, ConfigEntryBase> Synced = new Dictionary<string, ConfigEntryBase>();
	}
	public static class InventoryRows
	{
		internal static class Backdrop
		{
			private static InventoryGui _seen;

			private static int _shown = -1;

			private static readonly List<RectTransform> Panels = new List<RectTransform>();

			private static readonly List<float> Heights = new List<float>();

			private static RectTransform _container;

			private static Vector2 _containerBase;

			internal static void Invalidate()
			{
				_shown = -1;
			}

			internal static void Tick()
			{
				InventoryGui instance = InventoryGui.instance;
				if ((Object)(object)instance == (Object)null || (Object)(object)instance.m_player == (Object)null)
				{
					_seen = null;
					return;
				}
				if (instance != _seen)
				{
					_seen = instance;
					_shown = -1;
					Capture(instance);
				}
				int extra = Extra;
				if (extra != _shown)
				{
					_shown = extra;
					Resize(instance, extra);
				}
			}

			private static void Capture(InventoryGui gui)
			{
				//IL_004c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0051: Unknown result type (might be due to invalid IL or missing references)
				//IL_003c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0041: Unknown result type (might be due to invalid IL or missing references)
				//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
				//IL_00af: Unknown result type (might be due to invalid IL or missing references)
				Panels.Clear();
				Heights.Clear();
				Remember(gui.m_player);
				_container = gui.m_container;
				if ((Object)(object)_container != (Object)null)
				{
					_containerBase = _container.anchoredPosition;
				}
				Rect rect = gui.m_player.rect;
				float width = ((Rect)(ref rect)).width;
				Image[] componentsInChildren = ((Component)gui.m_player).GetComponentsInChildren<Image>(true);
				foreach (Image val in componentsInChildren)
				{
					if (!((Object)(object)val == (Object)null) && !((Object)(object)val.sprite == (Object)null) && ((Object)val.sprite).name.IndexOf("woodpanel", StringComparison.OrdinalIgnoreCase) >= 0)
					{
						rect = ((Graphic)val).rectTransform.rect;
						if (!(((Rect)(ref rect)).width < width * 0.6f))
						{
							Remember(((Graphic)val).rectTransform);
						}
					}
				}
			}

			private static void Remember(RectTransform rect)
			{
				//IL_0028: Unknown result type (might be due to invalid IL or missing references)
				//IL_002d: Unknown result type (might be due to invalid IL or missing references)
				if (!((Object)(object)rect == (Object)null) && !Panels.Contains(rect))
				{
					Panels.Add(rect);
					List<float> heights = Heights;
					Rect rect2 = rect.rect;
					heights.Add(((Rect)(ref rect2)).height);
				}
			}

			private static void Resize(InventoryGui gui, int rows)
			{
				//IL_0086: Unknown result type (might be due to invalid IL or missing references)
				//IL_0092: Unknown result type (might be due to invalid IL or missing references)
				//IL_0097: Unknown result type (might be due to invalid IL or missing references)
				InventoryGrid componentInChildren = ((Component)gui.m_player).GetComponentInChildren<InventoryGrid>(true);
				if ((Object)(object)componentInChildren == (Object)null || componentInChildren.m_elementSpace <= 0f)
				{
					return;
				}
				float num = (float)rows * componentInChildren.m_elementSpace;
				for (int i = 0; i < Panels.Count; i++)
				{
					if (!((Object)(object)Panels[i] == (Object)null))
					{
						Panels[i].SetSizeWithCurrentAnchors((Axis)1, Heights[i] + num);
					}
				}
				if ((Object)(object)_container != (Object)null)
				{
					_container.anchoredPosition = _containerBase + new Vector2(0f, 0f - num);
				}
			}
		}

		private static readonly Dictionary<string, int> Claims = new Dictionary<string, int>();

		private static FieldInfo _height;

		private static Player _player;

		private static int _base = -1;

		private static int _applied = -1;

		private static bool _widened;

		private static int _effective = -1;

		private const int LoadSlack = 16;

		public static int Total
		{
			get
			{
				int num = 0;
				foreach (KeyValuePair<string, int> claim in Claims)
				{
					num += claim.Value;
				}
				return num;
			}
		}

		public static int Extra
		{
			get
			{
				if (_base < 0 || _effective < 0)
				{
					return 0;
				}
				return Mathf.Max(0, _effective - _base);
			}
		}

		public static void Claim(string owner, int rows)
		{
			if (!string.IsNullOrEmpty(owner))
			{
				rows = Mathf.Max(0, rows);
				if (!Claims.TryGetValue(owner, out var value) || value != rows)
				{
					Claims[owner] = rows;
					_applied = -1;
				}
			}
		}

		internal static void Tick()
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				_player = null;
				_base = -1;
				_applied = -1;
				_effective = -1;
				return;
			}
			Inventory inventory = ((Humanoid)localPlayer).GetInventory();
			if (inventory == null)
			{
				return;
			}
			if (localPlayer != _player)
			{
				_player = localPlayer;
				_base = inventory.GetHeight();
				_applied = -1;
				_effective = -1;
				CorePlugin.Log.LogInfo((object)("Inventory rows: vanilla height is " + _base + "."));
			}
			if (Claims.Count == 0 && !_widened)
			{
				return;
			}
			int total = Total;
			if (total != _applied)
			{
				if (_height == null)
				{
					_height = AccessTools.Field(typeof(Inventory), "m_height");
				}
				if (_height == null)
				{
					CorePlugin.Log.LogError((object)"Inventory.m_height not found - extra rows cannot work.");
					_applied = total;
					return;
				}
				int num = _base + total;
				int num2 = ((Claims.Count > 0 && Occupied(inventory) > num) ? Compact(inventory, num) : 0);
				int num3 = Mathf.Max(num, Occupied(inventory));
				_applied = total;
				_widened = false;
				_effective = num3;
				_height.SetValue(inventory, num3);
				CorePlugin.Log.LogInfo((object)("Inventory rows: " + _base + " + " + total + " claimed by " + Claims.Count + " mod(s)" + ((num2 > 0) ? (", " + num2 + " item(s) moved up") : "") + ((num3 > num) ? (", held at " + num3 + " by items in the grid") : "") + "."));
				Backdrop.Invalidate();
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(Player), "Load")]
		private static void WidenBeforeLoad(Player __instance)
		{
			if ((Object)(object)__instance == (Object)null)
			{
				return;
			}
			Inventory inventory = ((Humanoid)__instance).GetInventory();
			if (inventory != null)
			{
				if (_height == null)
				{
					_height = AccessTools.Field(typeof(Inventory), "m_height");
				}
				if (!(_height == null))
				{
					_player = __instance;
					_base = inventory.GetHeight();
					_applied = -1;
					_widened = true;
					_effective = -1;
					_height.SetValue(inventory, _base + 16);
				}
			}
		}

		private static int Compact(Inventory inventory, int keep)
		{
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			if (keep < 1)
			{
				return 0;
			}
			List<ItemData> list = new List<ItemData>();
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				if (allItem != null && allItem.m_gridPos.y >= keep)
				{
					list.Add(allItem);
				}
			}
			if (list.Count == 0)
			{
				return 0;
			}
			int width = inventory.GetWidth();
			int num = 0;
			foreach (ItemData item in list)
			{
				Vector2i val = FreeSlot(inventory, width, keep);
				if (val.y < 0)
				{
					break;
				}
				item.m_gridPos = val;
				num++;
			}
			if (num > 0 && inventory.m_onChanged != null)
			{
				inventory.m_onChanged();
			}
			return num;
		}

		private static Vector2i FreeSlot(Inventory inventory, int width, int keep)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			for (int i = 0; i < keep; i++)
			{
				for (int j = 0; j < width; j++)
				{
					if (inventory.GetItemAt(j, i) == null)
					{
						return new Vector2i(j, i);
					}
				}
			}
			return new Vector2i(-1, -1);
		}

		private static int Occupied(Inventory inventory)
		{
			int num = 0;
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				if (allItem != null && allItem.m_gridPos.y + 1 > num)
				{
					num = allItem.m_gridPos.y + 1;
				}
			}
			return num;
		}
	}
	internal static class NetworkPatches
	{
		private struct RemoteMod
		{
			public string Version;

			public string Fingerprint;

			public string Data;
		}

		private const string RpcManifest = "Ezomic_Core_Manifest";

		private const string RpcConfig = "Ezomic_Core_Config";

		private static readonly Dictionary<ZRpc, Dictionary<string, RemoteMod>> Received = new Dictionary<ZRpc, Dictionary<string, RemoteMod>>();

		[HarmonyPostfix]
		[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
		private static void RegisterHandshake(ZNet __instance, ZNetPeer peer)
		{
			peer.m_rpc.Register<ZPackage>("Ezomic_Core_Manifest", (Action<ZRpc, ZPackage>)ReceiveManifest);
			peer.m_rpc.Register<ZPackage>("Ezomic_Core_Config", (Action<ZRpc, ZPackage>)ConfigSync.ReceiveConfig);
			peer.m_rpc.Invoke("Ezomic_Core_Manifest", new object[1] { BuildManifest() });
		}

		private static ZPackage BuildManifest()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(Suite.Mods.Count);
			foreach (KeyValuePair<string, ModEntry> mod in Suite.Mods)
			{
				val.Write(mod.Key);
				val.Write(mod.Value.Version ?? "");
				val.Write((int)mod.Value.Requirement);
				val.Write(mod.Value.Fingerprint ?? "");
				val.Write(mod.Value.Data ?? "");
			}
			return val;
		}

		private static void ReceiveManifest(ZRpc rpc, ZPackage pkg)
		{
			Dictionary<string, RemoteMod> dictionary = new Dictionary<string, RemoteMod>();
			int num = pkg.ReadInt();
			for (int i = 0; i < num; i++)
			{
				string key = pkg.ReadString();
				string version = pkg.ReadString();
				pkg.ReadInt();
				string fingerprint = ((pkg.GetPos() < pkg.Size()) ? pkg.ReadString() : "");
				string data = ((pkg.GetPos() < pkg.Size()) ? pkg.ReadString() : "");
				dictionary[key] = new RemoteMod
				{
					Version = version,
					Fingerprint = fingerprint,
					Data = data
				};
			}
			Received[rpc] = dictionary;
			if ((Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer() && CorePlugin.EnforceVersions.Value)
			{
				string text = Compare(dictionary);
				if (text != null)
				{
					CorePlugin.Log.LogError((object)("This server does not match your mods:\n" + text + "\nThe server will close the connection."));
					ConnectError.Expect(Screen(dictionary));
				}
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
		private static bool GateOnPeerInfo(ZNet __instance, ZRpc rpc)
		{
			if (!CorePlugin.EnforceVersions.Value)
			{
				return true;
			}
			if (!Received.TryGetValue(rpc, out var value))
			{
				value = new Dictionary<string, RemoteMod>();
			}
			string text = Compare(value);
			if (text == null)
			{
				return true;
			}
			if (__instance.IsServer())
			{
				CorePlugin.Log.LogWarning((object)("Refused a connection:\n" + text));
				rpc.Invoke("Error", new object[1] { 3 });
				return false;
			}
			CorePlugin.Log.LogError((object)("This server does not match your mods:\n" + text + "\nThe server will close the connection."));
			return true;
		}

		private static string Compare(Dictionary<string, RemoteMod> theirs, bool detailed = true)
		{
			StringBuilder builder = null;
			foreach (KeyValuePair<string, ModEntry> mod in Suite.Mods)
			{
				ModEntry value = mod.Value;
				if (!theirs.TryGetValue(mod.Key, out var value2))
				{
					if (value.Requirement != Requirement.HostOnly)
					{
						Append(ref builder, detailed ? ("  " + value.Name + " " + value.Version + " is missing on the other end.") : ("  " + value.Name + " - missing there"));
					}
				}
				else if (value2.Version != value.Version)
				{
					Append(ref builder, detailed ? ("  " + value.Name + ": they have " + value2.Version + ", this end has " + value.Version + ".") : ("  " + value.Name + " - " + value2.Version + " there, " + value.Version + " here"));
				}
				else if (CorePlugin.EnforceBuilds.Value && !string.IsNullOrEmpty(value.Fingerprint) && !string.IsNullOrEmpty(value2.Fingerprint))
				{
					if (value2.Fingerprint != value.Fingerprint)
					{
						Append(ref builder, detailed ? ("  " + value.Name + " " + value.Version + " is the same version on both ends but a different build (" + value2.Fingerprint + " there, " + value.Fingerprint + " here). Rebuild whichever is behind.") : ("  " + value.Name + " - different build"));
					}
					else if (!string.IsNullOrEmpty(value.Data) && !string.IsNullOrEmpty(value2.Data) && value2.Data != value.Data)
					{
						Append(ref builder, detailed ? ("  " + value.Name + " has a different data file (" + value2.Data + " there, " + value.Data + " here). Copy whichever is right to the other end.") : ("  " + value.Name + " - different data file"));
					}
				}
			}
			foreach (KeyValuePair<string, RemoteMod> their in theirs)
			{
				if (!Suite.Mods.ContainsKey(their.Key))
				{
					Append(ref builder, "  " + their.Key + " " + their.Value.Version + " is on the other end but not this one.");
				}
			}
			return builder?.ToString();
		}

		private static string Screen(Dictionary<string, RemoteMod> theirs)
		{
			string text = Compare(theirs, detailed: false);
			if (text == null)
			{
				return "";
			}
			return "This server does not match your mods:\n\n" + text + "\n\nThe full detail is in your log.";
		}

		private static void Append(ref StringBuilder builder, string line)
		{
			if (builder == null)
			{
				builder = new StringBuilder();
			}
			else
			{
				builder.Append('\n');
			}
			builder.Append(line);
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
		private static void PushConfigOnPeerInfo(ZNet __instance, ZRpc rpc)
		{
			if (__instance.IsServer() && CorePlugin.EnforceConfig.Value)
			{
				rpc.Invoke("Ezomic_Core_Config", new object[1] { ConfigSync.BuildConfig() });
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(ZNet), "Disconnect")]
		private static void ForgetPeer(ZNetPeer peer)
		{
			if (peer != null && peer.m_rpc != null)
			{
				Received.Remove(peer.m_rpc);
			}
		}
	}
	public static class Suite
	{
		internal static readonly Dictionary<string, ModEntry> Mods = new Dictionary<string, ModEntry>(StringComparer.Ordinal);

		private static string _lastRegistered;

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static void Register(string guid, string name, string version, ConfigFile config, Requirement requirement = Requirement.Everyone, Assembly owner = null)
		{
			if (string.IsNullOrEmpty(guid))
			{
				throw new ArgumentNullException("guid");
			}
			if (!Mods.TryGetValue(guid, out var value))
			{
				value = new ModEntry();
				Mods[guid] = value;
			}
			value.Guid = guid;
			value.Name = name;
			value.Version = version;
			value.Requirement = requirement;
			value.Config = config;
			value.Fingerprint = FingerprintOf(owner ?? Assembly.GetCallingAssembly());
			AbsorbConfig(value);
			_lastRegistered = guid;
			CorePlugin.Log.LogInfo((object)("Registered " + name + " " + version + " (" + requirement.ToString() + ") build " + value.Fingerprint));
		}

		private static string FingerprintOf(Assembly assembly)
		{
			if (assembly == null)
			{
				return "";
			}
			try
			{
				return assembly.ManifestModule.ModuleVersionId.ToString("N").Substring(0, 12);
			}
			catch (Exception ex)
			{
				CorePlugin.Log.LogWarning((object)("Could not read a build id: " + ex.Message));
				return "";
			}
		}

		public static void Data(string contents, string guid = null)
		{
			guid = guid ?? _lastRegistered;
			if (!string.IsNullOrEmpty(guid) && Mods.TryGetValue(guid, out var value))
			{
				value.Data = HashOf(contents);
				CorePlugin.Log.LogInfo((object)(value.Name + " data " + value.Data + "."));
			}
		}

		private static string HashOf(string contents)
		{
			if (string.IsNullOrEmpty(contents))
			{
				return "";
			}
			contents = contents.Replace("\r\n", "\n").Replace("\r", "\n");
			uint num = 2166136261u;
			for (int i = 0; i < contents.Length; i++)
			{
				num ^= contents[i];
				num *= 16777619;
			}
			return num.ToString("x8");
		}

		public static void Sync(ConfigEntryBase entry)
		{
			if (entry == null)
			{
				throw new ArgumentNullException("entry");
			}
			if (_lastRegistered == null || !Mods.ContainsKey(_lastRegistered))
			{
				throw new InvalidOperationException("Suite.Sync was called before Suite.Register. Register the mod first.");
			}
			Mods[_lastRegistered].Synced[Key(entry)] = entry;
		}

		public static void Sync(params ConfigEntryBase[] entries)
		{
			for (int i = 0; i < entries.Length; i++)
			{
				Sync(entries[i]);
			}
		}

		internal static void AbsorbConfig(ModEntry entry)
		{
			if (entry == null || entry.Config == null)
			{
				return;
			}
			foreach (ConfigDefinition key in entry.Config.Keys)
			{
				ConfigEntryBase val = entry.Config[key];
				if (val != null)
				{
					entry.Synced[key.Section + "." + key.Key] = val;
				}
			}
		}

		internal static string Key(ConfigEntryBase entry)
		{
			return entry.Definition.Section + "." + entry.Definition.Key;
		}

		public static void ExplainRefusal(string reason)
		{
			ConnectError.Expect(reason);
		}

		public static ConfigurationManagerAttributes Display(int order = 0, bool advanced = false, string name = null)
		{
			return new ConfigurationManagerAttributes
			{
				Order = -order,
				IsAdvanced = advanced,
				DispName = name
			};
		}
	}
	public sealed class ConfigurationManagerAttributes
	{
		public int? Order;

		public bool? Browsable;

		public bool? IsAdvanced;

		public bool? HideDefaultButton;

		public string DispName;

		public bool? ReadOnly;
	}
}