Decompiled source of Quads Map Share v1.1.5

plugins/MapShare.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using DHack.Shared;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyMetadata("AI_Assisted_Creation", "This assembly was partially or fully created with the assistance of Generative AI (Claude Code).")]
[assembly: AssemblyMetadata("AI_Model_Vendor", "Anthropic")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("0.0.0.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 MapShare
{
	[BepInPlugin("com.quad.mapshare", "MapShare", "1.1.5")]
	public class Plugin : BaseUnityPlugin
	{
		private class Assembly_
		{
			public int Total;

			public byte[][] Parts;

			public int Have;

			public float Started;
		}

		private class Answer
		{
			public int Rank;

			public float Due;

			public byte[] Seen;
		}

		public const string Guid = "com.quad.mapshare";

		public const string OldGuid = "com.dhack.mapshare";

		public const string Name = "MapShare";

		public const string Version = "1.1.5";

		private const string RpcCells = "DHack_MapCells";

		private const string RpcHello = "DHack_MapHello";

		private const string RpcFull = "DHack_MapFull";

		internal static Plugin Instance;

		internal static readonly List<int> Pending = new List<int>();

		internal static ServerSettings Synced;

		private ConfigEntry<bool> _send;

		private ConfigEntry<bool> _receive;

		private ConfigEntry<bool> _allowed;

		private ConfigEntry<float> _interval;

		private Harmony _harmony;

		private static readonly FieldInfo Explored = AccessTools.Field(typeof(Minimap), "m_explored");

		private static readonly FieldInfo ExploredOthers = AccessTools.Field(typeof(Minimap), "m_exploredOthers");

		private static readonly FieldInfo Fog = AccessTools.Field(typeof(Minimap), "m_fogTexture");

		private static readonly Func<Minimap, int, int, bool> ExploreOthers = (Func<Minimap, int, int, bool>)Delegate.CreateDelegate(typeof(Func<Minimap, int, int, bool>), AccessTools.Method(typeof(Minimap), "ExploreOthers", new Type[2]
		{
			typeof(int),
			typeof(int)
		}, (Type[])null));

		private int _awakeFrame;

		private ZRoutedRpc _registeredOn;

		private bool _helloPending;

		private float _nextSend;

		private float _helloAt;

		private float _helloSentAt = -1000f;

		private readonly Dictionary<long, Assembly_> _incoming = new Dictionary<long, Assembly_>();

		private readonly Dictionary<long, Answer> _answers = new Dictionary<long, Answer>();

		private const int MaxParts = 64;

		private const int MaxPiece = 65536;

		private const int MaxSets = 16;

		private const float SetTimeout = 60f;

		private const float PieceGap = 0.25f;

		private float _nextPiece;

		private bool Sending
		{
			get
			{
				if (_send.Value)
				{
					return _allowed.Value;
				}
				return false;
			}
		}

		private bool Receiving
		{
			get
			{
				if (_receive.Value)
				{
					return _allowed.Value;
				}
				return false;
			}
		}

		private void Awake()
		{
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Expected O, but got Unknown
			Migration.FromOldGuid((BaseUnityPlugin)(object)this, "com.dhack.mapshare");
			Translations.Load((BaseUnityPlugin)(object)this);
			Instance = this;
			_send = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShareMyMap", true, "Send the parts of the map you uncover to the other players in the world.");
			_receive = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ReceiveSharedMap", true, "Show the parts of the map other players uncover on your own map.");
			_interval = ((BaseUnityPlugin)this).Config.Bind<float>("General", "SendInterval", 2f, "Seconds between sending newly uncovered map cells.");
			Synced = new ServerSettings("com.quad.mapshare", ((BaseUnityPlugin)this).Config, ((BaseUnityPlugin)this).Logger);
			_allowed = Synced.Add<bool>(((BaseUnityPlugin)this).Config.Bind<bool>("General", "AllowSharing", true, "Players with this mod share the map they uncover with each other. A server that wants everyone to explore for themselves turns this off. In multiplayer the server's value applies."));
			_awakeFrame = Time.frameCount;
			_harmony = new Harmony("com.quad.mapshare");
			_harmony.PatchAll();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"MapShare 1.1.5 loaded");
			if ((Object)(object)Player.m_localPlayer != (Object)null && (Object)(object)Chat.instance != (Object)null)
			{
				((Terminal)Chat.instance).AddString("[Mod]", Translations.T("mapshare_chat_reloaded", "MapShare", "1.1.5"), (Type)1, false);
			}
		}

		private void OnDestroy()
		{
			Translations.Unload();
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			UnregisterRpc();
			Pending.Clear();
			_incoming.Clear();
			_answers.Clear();
			Synced?.Dispose();
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
		}

		private void Update()
		{
			Synced?.Update();
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance == null)
			{
				_registeredOn = null;
			}
			else
			{
				if (Time.frameCount <= _awakeFrame + 2)
				{
					return;
				}
				if (instance != _registeredOn)
				{
					_registeredOn = instance;
					UnregisterRpc();
					instance.Register<ZPackage>("DHack_MapCells", (Action<long, ZPackage>)OnCells);
					instance.Register("DHack_MapHello", (Action<long>)OnHello);
					instance.Register<ZPackage>("DHack_MapFull", (Action<long, ZPackage>)OnFull);
					_helloPending = true;
					_helloAt = Time.unscaledTime + 4f;
					Pending.Clear();
					_answers.Clear();
				}
				if ((Object)(object)Minimap.instance == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null)
				{
					return;
				}
				if (_helloPending && Time.unscaledTime >= _helloAt)
				{
					_helloPending = false;
					if (Receiving)
					{
						Send("DHack_MapHello", null, 0L);
						_helloSentAt = Time.unscaledTime;
					}
					((MonoBehaviour)this).StartCoroutine(SendOwnMapLater());
				}
				if (_answers.Count > 0)
				{
					AnswerDue();
				}
				if (Sending && Pending.Count > 0 && Time.unscaledTime >= _nextSend)
				{
					_nextSend = Time.unscaledTime + Mathf.Max(0.5f, _interval.Value);
					FlushPending();
				}
				else if (!Sending)
				{
					Pending.Clear();
				}
			}
		}

		private static void UnregisterRpc()
		{
			if (ZRoutedRpc.instance != null && AccessTools.Field(typeof(ZRoutedRpc), "m_functions").GetValue(ZRoutedRpc.instance) is IDictionary dictionary)
			{
				string[] array = new string[3] { "DHack_MapCells", "DHack_MapHello", "DHack_MapFull" };
				foreach (string text in array)
				{
					dictionary.Remove(StringExtensionMethods.GetStableHashCode(text));
				}
			}
		}

		private void Send(string rpc, ZPackage package, long target = 0L)
		{
			if (ZRoutedRpc.instance == null || (Object)(object)Player.m_localPlayer == (Object)null)
			{
				return;
			}
			try
			{
				if (package == null)
				{
					ZRoutedRpc.instance.InvokeRoutedRPC(target, rpc, Array.Empty<object>());
					return;
				}
				ZRoutedRpc.instance.InvokeRoutedRPC(target, rpc, new object[1] { package });
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not share the map: " + ex.Message));
			}
		}

		private void FlushPending()
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Expected O, but got Unknown
			while (Pending.Count > 0)
			{
				int num = Mathf.Min(Pending.Count, 4000);
				ZPackage val = new ZPackage();
				val.Write(1);
				val.Write(Minimap.instance.m_textureSize);
				val.Write(num);
				for (int i = 0; i < num; i++)
				{
					val.Write(Pending[i]);
				}
				Pending.RemoveRange(0, num);
				Send("DHack_MapCells", val, 0L);
			}
		}

		private void OnCells(long sender, ZPackage package)
		{
			if (!Receiving || (Object)(object)Minimap.instance == (Object)null || sender == ZDOMan.GetSessionID())
			{
				return;
			}
			try
			{
				package.ReadInt();
				int num = package.ReadInt();
				int num2 = package.ReadInt();
				if (num != Minimap.instance.m_textureSize || num2 < 0 || num2 > 200000)
				{
					return;
				}
				bool flag = false;
				for (int i = 0; i < num2; i++)
				{
					int num3 = package.ReadInt();
					if (num3 >= 0 && num3 < num * num && ExploreOthers(Minimap.instance, num3 % num, num3 / num))
					{
						flag = true;
					}
				}
				if (flag)
				{
					Finish();
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Bad map update: " + ex.Message));
			}
		}

		private static void Finish()
		{
			object? value = Fog.GetValue(Minimap.instance);
			Texture2D val = (Texture2D)((value is Texture2D) ? value : null);
			if (val != null)
			{
				val.Apply();
			}
			if ((Object)(object)Minimap.instance.m_sharedMapHint != (Object)null)
			{
				Minimap.instance.m_sharedMapHint.SetActive(false);
			}
		}

		private void OnHello(long sender)
		{
			if (Sending && sender != ZDOMan.GetSessionID() && !((Object)(object)Minimap.instance == (Object)null) && !_answers.ContainsKey(sender) && _answers.Count < 16)
			{
				int num = Rank(sender);
				if (num == 0)
				{
					((MonoBehaviour)this).StartCoroutine(SendFullMap(sender));
					return;
				}
				_answers[sender] = new Answer
				{
					Rank = num,
					Due = Time.unscaledTime + 20f + (float)num * 4f
				};
			}
		}

		private static int Rank(long joiner)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			long sessionID = ZDOMan.GetSessionID();
			int num = 0;
			if ((Object)(object)ZNet.instance == (Object)null)
			{
				return num;
			}
			foreach (PlayerInfo player in ZNet.instance.GetPlayerList())
			{
				ZDOID characterID = player.m_characterID;
				long userID = ((ZDOID)(ref characterID)).UserID;
				if (userID != 0L && userID != sessionID && userID != joiner && userID < sessionID)
				{
					num++;
				}
			}
			return num;
		}

		private void AnswerDue()
		{
			foreach (KeyValuePair<long, Answer> item in _answers.Where((KeyValuePair<long, Answer> kv) => Time.unscaledTime >= kv.Value.Due).ToList())
			{
				_answers.Remove(item.Key);
				if (Sending && !((Object)(object)Minimap.instance == (Object)null))
				{
					if (item.Value.Seen == null)
					{
						((MonoBehaviour)this).StartCoroutine(SendFullMap(item.Key));
					}
					else
					{
						((MonoBehaviour)this).StartCoroutine(SendFullMap(0L, item.Value.Seen));
					}
				}
			}
		}

		private void Heard(long sender, byte[] bits)
		{
			bool flag = Time.unscaledTime - _helloSentAt < 120f;
			foreach (KeyValuePair<long, Answer> answer in _answers)
			{
				Answer value = answer.Value;
				if (answer.Key == sender && value.Seen == null)
				{
					value.Seen = (byte[])bits.Clone();
					value.Due = Time.unscaledTime + (float)(value.Rank - 1) * 4f;
				}
				else if (value.Seen != null && (answer.Key == sender || !flag))
				{
					for (int i = 0; i < value.Seen.Length && i < bits.Length; i++)
					{
						value.Seen[i] |= bits[i];
					}
				}
			}
		}

		private IEnumerator SendOwnMapLater()
		{
			yield return (object)new WaitForSeconds(12f);
			if (Sending && (Object)(object)Minimap.instance != (Object)null)
			{
				yield return SendFullMap(0L);
			}
		}

		private IEnumerator SendFullMap(long target, byte[] minus = null)
		{
			Minimap map = Minimap.instance;
			BitArray mine = (BitArray)Explored.GetValue(map);
			BitArray others = (BitArray)ExploredOthers.GetValue(map);
			int n = mine.Length;
			byte[] bits = new byte[(n + 7) / 8];
			if (minus != null && minus.Length < bits.Length)
			{
				yield break;
			}
			bool any = false;
			for (int start = 0; start < n; start += 250000)
			{
				int num = Mathf.Min(n, start + 250000);
				for (int i = start; i < num; i++)
				{
					if ((mine[i] || others[i]) && (minus == null || (minus[i >> 3] & (1 << (i & 7))) == 0))
					{
						bits[i >> 3] |= (byte)(1 << (i & 7));
						any = true;
					}
				}
				yield return null;
			}
			if (minus != null && !any)
			{
				yield break;
			}
			byte[] packed = Utils.Compress(bits);
			int parts = (packed.Length + 30000 - 1) / 30000;
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Sending " + ((minus != null) ? "what a newcomer lacked" : "the whole map") + " to " + ((target == 0L) ? "everyone" : "one player") + ": " + $"{packed.Length / 1024} KB in {parts} piece(s)"));
			int id = Random.Range(1, int.MaxValue);
			for (int start = 0; start < parts; start++)
			{
				int num2 = Mathf.Min(30000, packed.Length - start * 30000);
				byte[] array = new byte[num2];
				Buffer.BlockCopy(packed, start * 30000, array, 0, num2);
				ZPackage package = new ZPackage();
				package.Write(id);
				package.Write(start);
				package.Write(parts);
				package.Write(map.m_textureSize);
				package.Write(array);
				while (Time.unscaledTime < _nextPiece)
				{
					yield return null;
				}
				_nextPiece = Time.unscaledTime + 0.25f;
				Send("DHack_MapFull", package, target);
			}
		}

		private void OnFull(long sender, ZPackage package)
		{
			if ((!Receiving && _answers.Count == 0) || (Object)(object)Minimap.instance == (Object)null || sender == ZDOMan.GetSessionID())
			{
				return;
			}
			try
			{
				int num = package.ReadInt();
				int num2 = package.ReadInt();
				int num3 = package.ReadInt();
				int num4 = package.ReadInt();
				byte[] array = package.ReadByteArray();
				if (num4 != Minimap.instance.m_textureSize || num3 <= 0 || num3 > 64 || num2 < 0 || num2 >= num3 || array == null || array.Length > 65536)
				{
					return;
				}
				foreach (long item in (from kv in _incoming
					where Time.unscaledTime - kv.Value.Started > 60f
					select kv.Key).ToList())
				{
					_incoming.Remove(item);
				}
				long key = sender * 31 + num;
				if (!_incoming.TryGetValue(key, out var value))
				{
					if (_incoming.Count >= 16)
					{
						return;
					}
					value = (_incoming[key] = new Assembly_
					{
						Total = num3,
						Parts = new byte[num3][],
						Started = Time.unscaledTime
					});
				}
				if (value.Total != num3)
				{
					return;
				}
				if (value.Parts[num2] == null)
				{
					value.Parts[num2] = array;
					value.Have++;
				}
				if (value.Have < value.Total)
				{
					return;
				}
				_incoming.Remove(key);
				byte[] packed = value.Parts.SelectMany((byte[] p) => p).ToArray();
				byte[] array2 = Unpack(packed, (num4 * num4 + 7) / 8);
				if (array2 == null)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"A shared map was not the size of this world's map: ignored");
					return;
				}
				Heard(sender, array2);
				if (Receiving)
				{
					((MonoBehaviour)this).StartCoroutine(Merge(array2));
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Bad map piece: " + ex.Message));
			}
		}

		private static byte[] Unpack(byte[] packed, int expected)
		{
			using MemoryStream stream = new MemoryStream(packed);
			using GZipStream gZipStream = new GZipStream(stream, CompressionMode.Decompress);
			byte[] array = new byte[expected];
			int i;
			int num;
			for (i = 0; i < expected; i += num)
			{
				if ((num = gZipStream.Read(array, i, expected - i)) <= 0)
				{
					break;
				}
			}
			if (i < expected || gZipStream.ReadByte() >= 0)
			{
				return null;
			}
			return array;
		}

		private IEnumerator Merge(byte[] bits)
		{
			Minimap map = Minimap.instance;
			BitArray mine = (BitArray)Explored.GetValue(map);
			BitArray others = (BitArray)ExploredOthers.GetValue(map);
			int size = map.m_textureSize;
			int n = size * size;
			int added = 0;
			if (bits.Length * 8 < n)
			{
				yield break;
			}
			for (int start = 0; start < n; start += 150000)
			{
				int num = Mathf.Min(n, start + 150000);
				for (int i = start; i < num; i++)
				{
					if ((bits[i >> 3] & (1 << (i & 7))) != 0 && !mine[i] && !others[i] && ExploreOthers(map, i % size, i / size))
					{
						added++;
					}
				}
				yield return null;
			}
			if (added > 0)
			{
				Finish();
				((BaseUnityPlugin)this).Logger.LogInfo((object)$"Added {added} map cells shared by other players");
			}
		}
	}
	[HarmonyPatch(typeof(Minimap), "Explore", new Type[]
	{
		typeof(int),
		typeof(int)
	})]
	internal static class Minimap_Explore
	{
		private static void Postfix(int x, int y, bool __result)
		{
			if (__result && !((Object)(object)Plugin.Instance == (Object)null) && !((Object)(object)Minimap.instance == (Object)null) && Plugin.Pending.Count < 200000)
			{
				Plugin.Pending.Add(y * Minimap.instance.m_textureSize + x);
			}
		}
	}
}
namespace DHack.Shared
{
	internal static class Migration
	{
		private sealed class Notice : MonoBehaviour
		{
			public string Text;

			private float _next;

			private void Update()
			{
				if (!(Time.unscaledTime < _next) && !((Object)(object)Player.m_localPlayer == (Object)null) && !((Object)(object)MessageHud.instance == (Object)null))
				{
					((Character)Player.m_localPlayer).Message((MessageType)2, Text, 0, (Sprite)null, false);
					_next = Time.unscaledTime + 60f;
				}
			}
		}

		public static void FromOldGuid(BaseUnityPlugin plugin, string oldGuid)
		{
			BepInPlugin metadata = MetadataHelper.GetMetadata((object)plugin);
			string text = ((metadata != null) ? metadata.GUID : null);
			if (string.IsNullOrEmpty(oldGuid) || string.IsNullOrEmpty(text) || oldGuid == text)
			{
				return;
			}
			ManualLogSource val = Logger.CreateLogSource(text);
			try
			{
				bool flag = false;
				if (Directory.Exists(Paths.ConfigPath))
				{
					string[] files = Directory.GetFiles(Paths.ConfigPath, oldGuid + ".*");
					foreach (string text2 in files)
					{
						string fileName = Path.GetFileName(text2);
						if (!fileName.EndsWith(".cfg") && !fileName.EndsWith(".json") && !fileName.EndsWith(".txt"))
						{
							continue;
						}
						string text3 = Path.Combine(Paths.ConfigPath, text + fileName.Substring(oldGuid.Length));
						if (!File.Exists(text3))
						{
							File.Copy(text2, text3);
							val.LogInfo((object)("Settings moved over from " + fileName + " (the mod's id is now " + text + "; the old file is kept as a backup)"));
							if (fileName == oldGuid + ".cfg")
							{
								flag = true;
							}
						}
					}
				}
				if (flag)
				{
					plugin.Config.Reload();
				}
			}
			catch (Exception ex)
			{
				val.LogWarning((object)("Couldn't move the settings over from " + oldGuid + ".cfg: " + ex.Message));
			}
			finally
			{
				Logger.Sources.Remove((ILogSource)(object)val);
			}
			((MonoBehaviour)plugin).StartCoroutine(StandDownIfTwin(plugin, oldGuid));
		}

		private static IEnumerator StandDownIfTwin(BaseUnityPlugin plugin, string oldGuid)
		{
			yield return null;
			yield return null;
			if (!((Object)(object)plugin == (Object)null) && Chainloader.PluginInfos.TryGetValue(oldGuid, out var value) && !((Object)(object)value.Instance == (Object)null) && !((Object)(object)value.Instance == (Object)(object)plugin))
			{
				BepInPlugin metadata = MetadataHelper.GetMetadata((object)plugin);
				string text = ((metadata != null) ? metadata.Name : null) ?? oldGuid;
				string text2 = (string.IsNullOrEmpty(value.Location) ? "an older copy" : value.Location);
				string text3 = text + ": two copies are installed, an old one (" + text2 + ") and this one. Both would run and do everything twice, so this one stands down. Delete the old copy, then restart the game.";
				Debug.LogError((object)("[" + text + "] " + text3));
				Object.Destroy((Object)(object)plugin);
				GameObject val = new GameObject("Migration_TwoCopies_" + text);
				Object.DontDestroyOnLoad((Object)(object)val);
				val.AddComponent<Notice>().Text = text3;
			}
		}
	}
	internal static class Translations
	{
		private sealed class Blank
		{
			public string Tag;

			public Func<string> Value;

			public Action Unhook;
		}

		public static readonly string[] Shipped = new string[9] { "English", "Russian", "German", "Chinese", "Portuguese_Brazilian", "Spanish", "French", "Polish", "Turkish" };

		private static readonly Dictionary<string, Dictionary<string, string>> Languages = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);

		private static Dictionary<string, string> _english = new Dictionary<string, string>();

		private static string _overrideDir;

		private static bool _hooked;

		private static readonly MethodInfo AddWord = AccessTools.Method(typeof(Localization), "AddWord", (Type[])null, (Type[])null);

		private static readonly FieldInfo Cache = AccessTools.Field(typeof(Localization), "m_cache");

		private static readonly FieldInfo GameWords = AccessTools.Field(typeof(Localization), "m_translations");

		private static Dictionary<string, string> _given = new Dictionary<string, string>();

		private static Dictionary<string, string> _files = new Dictionary<string, string>();

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

		private static readonly HashSet<string> _runtimeGiven = new HashSet<string>();

		private static readonly Dictionary<string, List<Blank>> Fills = new Dictionary<string, List<Blank>>();

		public static void Load(BaseUnityPlugin plugin)
		{
			Languages.Clear();
			Assembly assembly = ((object)plugin).GetType().Assembly;
			foreach (string item in from n in assembly.GetManifestResourceNames()
				where n.StartsWith("translations/") && n.EndsWith(".json")
				select n)
			{
				using StreamReader streamReader = new StreamReader(assembly.GetManifestResourceStream(item));
				Languages[Path.GetFileNameWithoutExtension(item.Substring("translations/".Length))] = Parse(streamReader.ReadToEnd());
			}
			_english = (Languages.TryGetValue("English", out var value) ? value : new Dictionary<string, string>());
			_overrideDir = Path.Combine(Paths.ConfigPath, "translations", plugin.Info.Metadata.GUID);
			if (!_hooked)
			{
				Localization.OnLanguageChange = (Action)Delegate.Combine(Localization.OnLanguageChange, new Action(Apply));
				_hooked = true;
			}
			Apply();
		}

		public static void Unload()
		{
			if (_hooked)
			{
				Localization.OnLanguageChange = (Action)Delegate.Remove(Localization.OnLanguageChange, new Action(Apply));
				_hooked = false;
			}
			foreach (List<Blank> value2 in Fills.Values)
			{
				foreach (Blank item in value2)
				{
					item.Unhook?.Invoke();
				}
			}
			Fills.Clear();
			Runtime.Clear();
			Localization val = ((_runtimeGiven.Count > 0) ? Localization.instance : null);
			if (val != null && AddWord != null)
			{
				Dictionary<string, string> dictionary = GameWords?.GetValue(val) as Dictionary<string, string>;
				foreach (string item2 in _runtimeGiven)
				{
					if (_files.TryGetValue(item2, out var value))
					{
						AddWord.Invoke(val, new object[2] { item2, value });
					}
					else
					{
						dictionary?.Remove(item2);
					}
				}
				EvictCache(val);
			}
			_runtimeGiven.Clear();
		}

		public static string T(string key, params object[] args)
		{
			string text = ((Localization.instance != null) ? Localization.instance.Localize("$" + key) : null);
			if (string.IsNullOrEmpty(text) || text == "[" + key + "]")
			{
				text = (_english.TryGetValue(key, out var value) ? FillIn(key, value) : key);
			}
			if (args == null || args.Length == 0)
			{
				return text;
			}
			try
			{
				return string.Format(text, args);
			}
			catch (FormatException)
			{
				string value2;
				return _english.TryGetValue(key, out value2) ? SafeFormat(FillIn(key, value2), args) : text;
			}
		}

		private static string SafeFormat(string text, object[] args)
		{
			try
			{
				return string.Format(text, args);
			}
			catch (FormatException)
			{
				return text;
			}
		}

		public static void Fill<TValue>(string key, string placeholder, ConfigEntry<TValue> setting, Func<TValue, string> format = null)
		{
			if (setting != null)
			{
				Fill(key, placeholder, () => (format == null) ? Text(setting.Value) : format(setting.Value), (ConfigEntryBase)setting);
			}
		}

		public static void Fill(string key, string placeholder, Func<string> value, params ConfigEntryBase[] settings)
		{
			if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(placeholder) || value == null)
			{
				return;
			}
			key = key.TrimStart(new char[1] { '$' });
			string tag = (placeholder.StartsWith("{") ? placeholder : ("{" + placeholder + "}"));
			if (!Fills.TryGetValue(key, out var value2))
			{
				value2 = (Fills[key] = new List<Blank>());
			}
			int num = value2.FindIndex((Blank b) => b.Tag == tag);
			if (num >= 0)
			{
				value2[num].Unhook?.Invoke();
				value2.RemoveAt(num);
			}
			Blank blank = new Blank
			{
				Tag = tag,
				Value = value
			};
			if (settings != null)
			{
				EventHandler changed = delegate
				{
					Show(key);
				};
				foreach (ConfigEntryBase setting in settings.Where((ConfigEntryBase s) => s != null).Distinct())
				{
					EventInfo ev = ((object)setting).GetType().GetEvent("SettingChanged");
					if (!(ev == null))
					{
						ev.AddEventHandler(setting, changed);
						blank.Unhook = (Action)Delegate.Combine(blank.Unhook, (Action)delegate
						{
							ev.RemoveEventHandler(setting, changed);
						});
					}
				}
			}
			value2.Add(blank);
			Show(key);
		}

		public static void Refresh()
		{
			Localization instance = Localization.instance;
			if (instance == null || AddWord == null)
			{
				return;
			}
			foreach (string key in Fills.Keys)
			{
				if (_given.TryGetValue(key, out var value))
				{
					AddWord.Invoke(instance, new object[2]
					{
						key,
						FillIn(key, value)
					});
				}
			}
			EvictCache(instance);
		}

		private static void Show(string key)
		{
			Localization instance = Localization.instance;
			if (instance != null && !(AddWord == null) && _given.TryGetValue(key, out var value))
			{
				AddWord.Invoke(instance, new object[2]
				{
					key,
					FillIn(key, value)
				});
				EvictCache(instance);
			}
		}

		private static string FillIn(string key, string text)
		{
			if (text == null || !Fills.TryGetValue(key, out var value))
			{
				return text;
			}
			foreach (Blank item in value)
			{
				if (text.IndexOf(item.Tag, StringComparison.Ordinal) >= 0)
				{
					try
					{
						text = text.Replace(item.Tag, item.Value() ?? "");
					}
					catch (Exception ex)
					{
						Debug.LogWarning((object)("Translations: filling " + item.Tag + " in " + key + ": " + ex.Message));
					}
				}
			}
			return text;
		}

		private static string Text(object value)
		{
			object obj;
			if (!(value is IFormattable formattable))
			{
				obj = value?.ToString();
				if (obj == null)
				{
					return "";
				}
			}
			else
			{
				obj = formattable.ToString(null, CultureInfo.InvariantCulture);
			}
			return (string)obj;
		}

		public static void AddWords(string group, string language, IDictionary<string, string> words)
		{
			if (!string.IsNullOrEmpty(group) && !string.IsNullOrEmpty(language))
			{
				SetWords(group, language, words);
				Apply();
			}
		}

		public static void AddWords(string group, IDictionary<string, Dictionary<string, string>> languages)
		{
			if (string.IsNullOrEmpty(group))
			{
				return;
			}
			Runtime.Remove(group);
			if (languages != null)
			{
				foreach (KeyValuePair<string, Dictionary<string, string>> language in languages)
				{
					if (!string.IsNullOrEmpty(language.Key))
					{
						SetWords(group, language.Key, language.Value);
					}
				}
			}
			Apply();
		}

		public static void RemoveWords(string group)
		{
			if (group != null && Runtime.Remove(group))
			{
				Apply();
			}
		}

		public static Dictionary<string, string> ParseJson(string json)
		{
			if (!string.IsNullOrEmpty(json))
			{
				return Parse(json);
			}
			return new Dictionary<string, string>();
		}

		private static void SetWords(string group, string language, IDictionary<string, string> words)
		{
			if (!Runtime.TryGetValue(group, out var value))
			{
				value = (Runtime[group] = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase));
			}
			if (words == null || words.Count == 0)
			{
				value.Remove(language);
				return;
			}
			Dictionary<string, string> dictionary2 = new Dictionary<string, string>();
			foreach (KeyValuePair<string, string> word in words)
			{
				if (!string.IsNullOrEmpty(word.Key) && word.Value != null)
				{
					dictionary2[word.Key.TrimStart(new char[1] { '$' })] = word.Value;
				}
			}
			value[language] = dictionary2;
		}

		private static void AddRuntime(Dictionary<string, string> words, string language)
		{
			foreach (Dictionary<string, Dictionary<string, string>> value2 in Runtime.Values)
			{
				if (!value2.TryGetValue(language, out var value))
				{
					continue;
				}
				foreach (KeyValuePair<string, string> item in value)
				{
					words[item.Key] = item.Value;
				}
			}
		}

		private static void Apply()
		{
			Localization instance = Localization.instance;
			if (instance == null || AddWord == null)
			{
				return;
			}
			string selectedLanguage = instance.GetSelectedLanguage();
			Dictionary<string, string> dictionary = new Dictionary<string, string>(_english);
			Dictionary<string, string> dictionary2 = new Dictionary<string, string>(_english);
			AddRuntime(dictionary, "English");
			if (!string.Equals(selectedLanguage, "English", StringComparison.OrdinalIgnoreCase))
			{
				if (Languages.TryGetValue(selectedLanguage, out var value))
				{
					foreach (KeyValuePair<string, string> item in value)
					{
						dictionary[item.Key] = item.Value;
						dictionary2[item.Key] = item.Value;
					}
				}
				AddRuntime(dictionary, selectedLanguage);
			}
			try
			{
				string path = Path.Combine(_overrideDir ?? "", selectedLanguage + ".json");
				if (_overrideDir != null && File.Exists(path))
				{
					foreach (KeyValuePair<string, string> item2 in Parse(File.ReadAllText(path)))
					{
						dictionary[item2.Key] = item2.Value;
						dictionary2[item2.Key] = item2.Value;
					}
				}
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("Translations: couldn't read the player's " + selectedLanguage + " file: " + ex.Message));
			}
			Dictionary<string, string> dictionary3 = GameWords?.GetValue(instance) as Dictionary<string, string>;
			foreach (string item3 in _runtimeGiven)
			{
				if (!dictionary.ContainsKey(item3))
				{
					dictionary3?.Remove(item3);
				}
			}
			_runtimeGiven.Clear();
			foreach (Dictionary<string, Dictionary<string, string>> value2 in Runtime.Values)
			{
				foreach (Dictionary<string, string> value3 in value2.Values)
				{
					foreach (string key in value3.Keys)
					{
						_runtimeGiven.Add(key);
					}
				}
			}
			_given = dictionary;
			_files = dictionary2;
			foreach (KeyValuePair<string, string> item4 in dictionary)
			{
				AddWord.Invoke(instance, new object[2]
				{
					item4.Key,
					FillIn(item4.Key, item4.Value)
				});
			}
			EvictCache(instance);
		}

		private static void EvictCache(Localization loc)
		{
			object obj = Cache?.GetValue(loc);
			obj?.GetType().GetMethod("EvictAll")?.Invoke(obj, null);
		}

		private static Dictionary<string, string> Parse(string json)
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>();
			foreach (Match item in Regex.Matches(json, "\"((?:[^\"\\\\]|\\\\.)*)\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)\""))
			{
				dictionary[Unescape(item.Groups[1].Value)] = Unescape(item.Groups[2].Value);
			}
			return dictionary;
		}

		private static string Unescape(string s)
		{
			return Regex.Replace(s, "\\\\(u[0-9a-fA-F]{4}|.)", delegate(Match m)
			{
				string value = m.Groups[1].Value;
				return value[0] switch
				{
					'n' => "\n", 
					't' => "\t", 
					'u' => ((char)Convert.ToInt32(value.Substring(1), 16)).ToString(), 
					_ => value, 
				};
			});
		}
	}
	internal sealed class ServerSettings : IDisposable
	{
		public enum ServerMod
		{
			NotConnected,
			Checking,
			Installed,
			Missing
		}

		private sealed class ConfigurationManagerAttributes
		{
			public bool? ReadOnly;
		}

		private sealed class SyncedText
		{
			public string Key;

			public Func<string> Own;

			public Action<string> Use;

			public string Given;

			public bool FromServer;

			public bool Pending;

			public byte[] Packed;

			public bool TooBig;

			public MemoryStream Incoming;

			public int NextPart;
		}

		private sealed class Sending
		{
			public ZRpc Rpc;

			public string Key;

			public byte[] Data;

			public int Next;

			public int Parts;
		}

		private const int Protocol = 2;

		private const float ChangeSendGap = 0.5f;

		private const float AnswerWait = 20f;

		private const float FileWait = 1f;

		private const int PartSize = 65536;

		private const int CompressFrom = 1024;

		private const int WarnBytes = 262144;

		private const int MaxBytes = 8388608;

		private const int PaceBelow = 10240;

		private readonly string _send;

		private readonly string _ask;

		private readonly string _dAsk;

		private readonly string _dHello;

		private readonly string _dSend;

		private readonly string _edit;

		private readonly string _dTextAsk;

		private readonly string _dText;

		private readonly string _dAgain;

		private readonly ConfigFile _config;

		private readonly ManualLogSource _log;

		private readonly string _name;

		private readonly List<ConfigEntryBase> _entries = new List<ConfigEntryBase>();

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

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

		private readonly List<GameObject> _guardedPieces = new List<GameObject>();

		private readonly List<Recipe> _guardedRecipes = new List<Recipe>();

		private ZRoutedRpc _registeredOn;

		private bool _applying;

		private bool _reloading;

		private bool _changePending;

		private float _nextChangeSend;

		private float _askedAt = -1f;

		private int _serverProtocol;

		private bool _directSeen;

		private readonly HashSet<ZRpc> _directPeers = new HashSet<ZRpc>();

		private readonly List<SyncedText> _texts = new List<SyncedText>();

		private readonly Dictionary<ZRpc, HashSet<string>> _textPeers = new Dictionary<ZRpc, HashSet<string>>();

		private bool _textPending;

		private float _nextTextSend;

		private bool _readOnlyShown;

		private float _nextAdminCheck;

		private FileSystemWatcher _watcher;

		private volatile bool _fileTouched;

		private float _reloadAt = -1f;

		private string _fileText;

		private readonly List<Sending> _outbox = new List<Sending>();

		private static readonly MethodInfo UpdateAvailablePieces = AccessTools.Method(typeof(Player), "UpdateAvailablePiecesList", (Type[])null, (Type[])null);

		private static readonly List<ServerSettings> Live = new List<ServerSettings>();

		private static Harmony _saveHarmony;

		public string Version { get; }

		public bool FromServer => _server.Count > 0;

		public ServerMod Server { get; private set; }

		public string ServerVersion { get; private set; }

		public bool CanEditServer
		{
			get
			{
				if (!IsServer)
				{
					if (Server == ServerMod.Installed && _serverProtocol >= 1 && (Object)(object)ZNet.instance != (Object)null)
					{
						return ZNet.instance.LocalPlayerIsAdminOrHost();
					}
					return false;
				}
				return true;
			}
		}

		private static bool IsServer
		{
			get
			{
				if ((Object)(object)ZNet.instance != (Object)null)
				{
					return ZNet.instance.IsServer();
				}
				return false;
			}
		}

		public event Action ServerChanged;

		public ServerSettings(string guid, ConfigFile config, ManualLogSource log)
		{
			_send = guid + ".settings";
			_ask = guid + ".settings.ask";
			_dAsk = guid + ".settings.d.ask";
			_dHello = guid + ".settings.d.hello";
			_dSend = guid + ".settings.d.values";
			_edit = guid + ".settings.d.edit";
			_dTextAsk = guid + ".settings.d.textask";
			_dAgain = guid + ".settings.d.again";
			_dText = guid + ".settings.d.text";
			_config = config;
			_log = log;
			BepInPlugin val = PluginOf(guid);
			_name = ((val != null) ? val.Name : null) ?? guid;
			Version = ((val == null) ? null : val.Version?.ToString()) ?? "";
			Live.Add(this);
			if (Live.Count == 1)
			{
				PatchSave();
			}
			WatchFile();
		}

		public ConfigEntry<T> Add<T>(ConfigEntry<T> entry)
		{
			if (entry == null || _entries.Contains((ConfigEntryBase)(object)entry))
			{
				return entry;
			}
			_entries.Add((ConfigEntryBase)(object)entry);
			entry.SettingChanged += OnChanged;
			GiveManagerAttributes((ConfigEntryBase)(object)entry);
			return entry;
		}

		private static void GiveManagerAttributes(ConfigEntryBase entry)
		{
			ConfigDescription description = entry.Description;
			if (description == null || description == ConfigDescription.Empty || (description.Tags ?? new object[0]).Any(IsManagerAttributes))
			{
				return;
			}
			try
			{
				object[] value = new object[1]
				{
					new ConfigurationManagerAttributes()
				}.Concat(description.Tags ?? new object[0]).ToArray();
				AccessTools.Field(typeof(ConfigDescription), "<Tags>k__BackingField")?.SetValue(description, value);
			}
			catch (Exception)
			{
			}
		}

		public ConfigEntry<T> Bind<T>(string section, string key, T value, string description, AcceptableValueBase range = null, params object[] tags)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Expected O, but got Unknown
			if (!description.Contains("[Synced with Server]"))
			{
				description += " [Synced with Server]";
			}
			return Add<T>(_config.Bind<T>(section, key, value, new ConfigDescription(description, range, tags ?? new object[0])));
		}

		public void NeedsServer(GameObject piecePrefab)
		{
			if (!((Object)(object)piecePrefab == (Object)null))
			{
				_guardedPieces.RemoveAll((GameObject p) => (Object)(object)p == (Object)null || ((Object)p).name == ((Object)piecePrefab).name);
				_guardedPieces.Add(piecePrefab);
				Guard();
			}
		}

		public void NeedsServer(Recipe recipe)
		{
			if (!((Object)(object)recipe == (Object)null))
			{
				_guardedRecipes.RemoveAll((Recipe r) => (Object)(object)r == (Object)null || (Object)(object)r == (Object)(object)recipe || (!string.IsNullOrEmpty(((Object)recipe).name) && ((Object)r).name == ((Object)recipe).name));
				_guardedRecipes.Add(recipe);
				Guard();
			}
		}

		public void AddText(string key, Func<string> own, Action<string> use)
		{
			if (string.IsNullOrEmpty(key) || own == null || use == null)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogError((object)"AddText needs a key, own and use");
				}
				return;
			}
			_texts.RemoveAll((SyncedText x) => x.Key == key);
			SyncedText syncedText = new SyncedText
			{
				Key = key,
				Own = own,
				Use = use
			};
			_texts.Add(syncedText);
			Give(syncedText, OwnText(syncedText));
			ZRpc val = ((!((Object)(object)ZNet.instance != (Object)null) || IsServer) ? null : ZNet.instance.GetServerPeer()?.m_rpc);
			if (_registeredOn != null && val != null)
			{
				val.Register<ZPackage>(_dText, (Action<ZRpc, ZPackage>)OnText);
				AskTexts(val);
			}
		}

		public void TextChanged(string key)
		{
			SyncedText syncedText = _texts.FirstOrDefault((SyncedText x) => x.Key == key);
			if (syncedText != null)
			{
				syncedText.Packed = null;
				if (!syncedText.FromServer)
				{
					Give(syncedText, OwnText(syncedText));
				}
				if (IsServer)
				{
					syncedText.Pending = true;
					_textPending = true;
				}
			}
		}

		public bool TextFromServer(string key)
		{
			return _texts.Any((SyncedText x) => x.Key == key && x.FromServer);
		}

		public void Update()
		{
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance != _registeredOn)
			{
				Unregister();
				Restore();
				RestoreTexts(give: true);
				_changePending = false;
				_textPending = false;
				SetServer(ServerMod.NotConnected, null, 0);
				if (instance != null)
				{
					Register(instance);
				}
			}
			float unscaledTime = Time.unscaledTime;
			if (_changePending && unscaledTime >= _nextChangeSend)
			{
				SendChange();
			}
			if (_textPending && unscaledTime >= _nextTextSend)
			{
				SendTexts();
			}
			if (_outbox.Count > 0)
			{
				Pump();
			}
			if (Server == ServerMod.Checking && _askedAt >= 0f && unscaledTime - _askedAt > 20f)
			{
				if (_guardedPieces.Count + _guardedRecipes.Count > 0)
				{
					ManualLogSource log = _log;
					if (log != null)
					{
						log.LogWarning((object)("The server didn't answer: " + _name + " isn't installed there (or its copy is older than 2026-10-11), so its pieces and items can't be made on this server."));
					}
				}
				else
				{
					ManualLogSource log2 = _log;
					if (log2 != null)
					{
						log2.LogInfo((object)("The server didn't answer: " + _name + " isn't installed there, or its copy is older" + (FromServer ? " (its settings apply)." : " (your own settings apply).")));
					}
				}
				SetServer(ServerMod.Missing, null, 0);
			}
			if (_fileTouched)
			{
				_fileTouched = false;
				_reloadAt = unscaledTime + 1f;
			}
			if (_reloadAt >= 0f && unscaledTime >= _reloadAt)
			{
				_reloadAt = -1f;
				ReloadFile();
			}
			if (FromServer && unscaledTime >= _nextAdminCheck)
			{
				_nextAdminCheck = unscaledTime + 1f;
				ShowReadOnly();
			}
		}

		public void Dispose()
		{
			Unregister();
			Restore();
			RestoreTexts(give: false);
			SetServer(ServerMod.NotConnected, null, 0);
			foreach (ConfigEntryBase entry in _entries)
			{
				Unsubscribe(entry);
			}
			_entries.Clear();
			_texts.Clear();
			try
			{
				_watcher?.Dispose();
			}
			catch (Exception)
			{
			}
			_watcher = null;
			Live.Remove(this);
			if (Live.Count == 0)
			{
				UnpatchSave();
			}
		}

		private void Register(ZRoutedRpc rpc)
		{
			Forget(rpc, _send);
			Forget(rpc, _ask);
			rpc.Register<ZPackage>(_send, (Action<long, ZPackage>)OnRoutedValues);
			rpc.Register(_ask, (Action<long>)OnRoutedAsk);
			rpc.m_onNewPeer = (Action<long>)Delegate.Combine(rpc.m_onNewPeer, new Action<long>(OnNewPeer));
			_registeredOn = rpc;
			if (IsServer)
			{
				foreach (ZNetPeer peer in ZNet.instance.GetPeers())
				{
					ListenTo(peer);
					if (peer != null)
					{
						ZRpc rpc2 = peer.m_rpc;
						if (rpc2 != null)
						{
							rpc2.Invoke(_dAgain, Array.Empty<object>());
						}
					}
				}
				return;
			}
			if ((Object)(object)ZNet.instance != (Object)null)
			{
				SetServer(ServerMod.Checking, null, 0);
				if (ZNet.instance.GetServerPeer() != null)
				{
					AskServer();
				}
			}
		}

		private void Unregister()
		{
			if (_registeredOn == null)
			{
				return;
			}
			ZRoutedRpc registeredOn = _registeredOn;
			registeredOn.m_onNewPeer = (Action<long>)Delegate.Remove(registeredOn.m_onNewPeer, new Action<long>(OnNewPeer));
			Forget(_registeredOn, _send);
			Forget(_registeredOn, _ask);
			if ((Object)(object)ZNet.instance != (Object)null)
			{
				foreach (ZNetPeer peer in ZNet.instance.GetPeers())
				{
					if (peer?.m_rpc != null)
					{
						string[] array = new string[7] { _dAsk, _dHello, _dSend, _edit, _dTextAsk, _dText, _dAgain };
						foreach (string name in array)
						{
							ForgetDirect(peer.m_rpc, name);
						}
					}
				}
			}
			_directPeers.Clear();
			_textPeers.Clear();
			_outbox.Clear();
			_directSeen = false;
			_registeredOn = null;
		}

		private static void Forget(ZRoutedRpc rpc, string name)
		{
			if (AccessTools.Field(typeof(ZRoutedRpc), "m_functions")?.GetValue(rpc) is IDictionary dictionary)
			{
				dictionary.Remove(StringExtensionMethods.GetStableHashCode(name));
			}
		}

		private static void ForgetDirect(ZRpc rpc, string name)
		{
			if (AccessTools.Field(typeof(ZRpc), "m_functions")?.GetValue(rpc) is IDictionary dictionary)
			{
				dictionary.Remove(StringExtensionMethods.GetStableHashCode(name));
			}
		}

		private void OnNewPeer(long uid)
		{
			if (IsServer)
			{
				ZNetPeer peer = ZNet.instance.GetPeer(uid);
				ListenTo(peer);
				if (peer?.m_rpc != null)
				{
					SendDirect(peer.m_rpc);
				}
				SendRouted(uid);
			}
			else if ((Object)(object)ZNet.instance != (Object)null)
			{
				ZNetPeer serverPeer = ZNet.instance.GetServerPeer();
				if (serverPeer != null && serverPeer.m_uid == uid)
				{
					AskServer();
				}
			}
		}

		private void ListenTo(ZNetPeer peer)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Expected O, but got Unknown
			if (peer?.m_rpc != null)
			{
				peer.m_rpc.Register(_dAsk, new Method(OnDirectAsk));
				peer.m_rpc.Register<ZPackage>(_edit, (Action<ZRpc, ZPackage>)OnEdit);
				peer.m_rpc.Register<ZPackage>(_dTextAsk, (Action<ZRpc, ZPackage>)OnTextAsk);
			}
		}

		private void OnDirectAsk(ZRpc rpc)
		{
			if (IsServer)
			{
				_directPeers.Add(rpc);
				SendDirect(rpc);
			}
		}

		private void OnRoutedAsk(long sender)
		{
			if (IsServer)
			{
				SendRouted(sender);
			}
		}

		private void SendDirect(ZRpc rpc)
		{
			//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(2);
			val.Write(Version);
			rpc.Invoke(_dHello, new object[1] { val });
			if (_entries.Count > 0)
			{
				rpc.Invoke(_dSend, new object[1] { Values() });
			}
		}

		private void SendRouted(long target)
		{
			if (ZRoutedRpc.instance != null && _entries.Count > 0)
			{
				ZRoutedRpc.instance.InvokeRoutedRPC(target, _send, new object[1] { Values() });
			}
		}

		private ZPackage Values()
		{
			//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(_entries.Count);
			foreach (ConfigEntryBase entry in _entries)
			{
				val.Write(entry.Definition.Section);
				val.Write(entry.Definition.Key);
				val.Write(TomlTypeConverter.ConvertToString(entry.BoxedValue, entry.SettingType));
			}
			return val;
		}

		private void AskServer()
		{
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Expected O, but got Unknown
			ZNetPeer val = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetServerPeer() : null);
			if (val?.m_rpc == null)
			{
				return;
			}
			_askedAt = Time.unscaledTime;
			val.m_rpc.Register<ZPackage>(_dHello, (Action<ZRpc, ZPackage>)OnDirectHello);
			val.m_rpc.Register<ZPackage>(_dSend, (Action<ZRpc, ZPackage>)OnDirectValues);
			val.m_rpc.Register<ZPackage>(_dText, (Action<ZRpc, ZPackage>)OnText);
			val.m_rpc.Register(_dAgain, new Method(OnAskAgain));
			val.m_rpc.Invoke(_dAsk, Array.Empty<object>());
			AskTexts(val.m_rpc);
			try
			{
				ZRoutedRpc instance = ZRoutedRpc.instance;
				if (instance != null)
				{
					instance.InvokeRoutedRPC(val.m_uid, _ask, Array.Empty<object>());
				}
			}
			catch (Exception)
			{
			}
		}

		private static bool IsServerConnection(ZRpc rpc)
		{
			if (!IsServer)
			{
				ZNet instance = ZNet.instance;
				return ((instance == null) ? null : instance.GetServerPeer()?.m_rpc) == rpc;
			}
			return false;
		}

		private void OnAskAgain(ZRpc rpc)
		{
			if (IsServerConnection(rpc))
			{
				rpc.Invoke(_dAsk, Array.Empty<object>());
				AskTexts(rpc);
			}
		}

		private void OnDirectHello(ZRpc rpc, ZPackage pkg)
		{
			if (!IsServerConnection(rpc))
			{
				return;
			}
			try
			{
				int protocol = pkg.ReadInt();
				string text = pkg.ReadString();
				if (text != Version && text != ServerVersion)
				{
					ManualLogSource log = _log;
					if (log != null)
					{
						log.LogWarning((object)("The server has " + _name + " " + text + ", you have " + Version + ": some things may not match. Use the same version as the server."));
					}
				}
				_directSeen = true;
				SetServer(ServerMod.Installed, text, protocol);
			}
			catch (Exception ex)
			{
				ManualLogSource log2 = _log;
				if (log2 != null)
				{
					log2.LogWarning((object)("Could not read the server's hello: " + ex.Message));
				}
			}
		}

		private void OnDirectValues(ZRpc rpc, ZPackage pkg)
		{
			if (IsServerConnection(rpc))
			{
				TakeValues(pkg);
			}
		}

		private void OnRoutedValues(long sender, ZPackage pkg)
		{
			if (!_directSeen && !IsServer)
			{
				ZNetPeer val = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetServerPeer() : null);
				if (val != null && val.m_uid == sender)
				{
					TakeValues(pkg);
				}
			}
		}

		private void TakeValues(ZPackage pkg)
		{
			try
			{
				int num = pkg.ReadInt();
				List<string> list = new List<string>();
				for (int i = 0; i < num; i++)
				{
					string section = pkg.ReadString();
					string key = pkg.ReadString();
					string text = pkg.ReadString();
					ConfigEntryBase val = ((IEnumerable<ConfigEntryBase>)_entries).FirstOrDefault((Func<ConfigEntryBase, bool>)((ConfigEntryBase x) => x.Definition.Section == section && x.Definition.Key == key));
					if (val != null)
					{
						object obj;
						try
						{
							obj = TomlTypeConverter.ConvertToValue(text, val.SettingType);
						}
						catch (Exception)
						{
							continue;
						}
						if (!_own.ContainsKey(val))
						{
							_own[val] = val.BoxedValue;
						}
						_server[val] = obj;
						if (!object.Equals(val.BoxedValue, obj))
						{
							Apply(val, obj);
							list.Add(section + "/" + key + " = " + text);
						}
					}
				}
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogInfo((object)($"Settings from the server apply while you play here ({_server.Count})" + ((list.Count > 0) ? (": " + string.Join(", ", list.ToArray())) : "")));
				}
				ShowReadOnly();
			}
			catch (Exception ex2)
			{
				ManualLogSource log2 = _log;
				if (log2 != null)
				{
					log2.LogWarning((object)("Could not read the server's settings: " + ex2.Message));
				}
			}
		}

		private void Apply(ConfigEntryBase e, object value)
		{
			bool saveOnConfigSet = _config.SaveOnConfigSet;
			_applying = true;
			try
			{
				_config.SaveOnConfigSet = false;
				e.BoxedValue = value;
			}
			finally
			{
				_config.SaveOnConfigSet = saveOnConfigSet;
				_applying = false;
			}
		}

		private void Restore()
		{
			if (_server.Count == 0)
			{
				return;
			}
			foreach (KeyValuePair<ConfigEntryBase, object> item in _own)
			{
				Apply(item.Key, item.Value);
			}
			_own.Clear();
			_server.Clear();
			ShowReadOnly();
			ManualLogSource log = _log;
			if (log != null)
			{
				log.LogInfo((object)"Your own settings apply again");
			}
		}

		private void OnChanged(object sender, EventArgs args)
		{
			if (_applying)
			{
				return;
			}
			ConfigEntryBase val = (ConfigEntryBase)((sender is ConfigEntryBase) ? sender : null);
			if (val == null)
			{
				return;
			}
			if (_server.TryGetValue(val, out var value))
			{
				if (!_reloading && CanEditServer)
				{
					SendEdit(val);
				}
				else
				{
					_own[val] = val.BoxedValue;
				}
				Apply(val, value);
			}
			else if (IsServer)
			{
				_changePending = true;
				if (Time.unscaledTime >= _nextChangeSend)
				{
					SendChange();
				}
			}
		}

		private void SendChange()
		{
			_changePending = false;
			_nextChangeSend = Time.unscaledTime + 0.5f;
			if (!IsServer)
			{
				return;
			}
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				if (peer?.m_rpc == null)
				{
					continue;
				}
				if (_directPeers.Contains(peer.m_rpc))
				{
					if (_entries.Count > 0)
					{
						peer.m_rpc.Invoke(_dSend, new object[1] { Values() });
					}
				}
				else
				{
					SendRouted(peer.m_uid);
				}
			}
		}

		private void SendEdit(ConfigEntryBase e)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Expected O, but got Unknown
			ZNet instance = ZNet.instance;
			ZNetPeer val = ((instance != null) ? instance.GetServerPeer() : null);
			if (val?.m_rpc != null)
			{
				ZPackage val2 = new ZPackage();
				val2.Write(e.Definition.Section);
				val2.Write(e.Definition.Key);
				val2.Write(TomlTypeConverter.ConvertToString(e.BoxedValue, e.SettingType));
				val.m_rpc.Invoke(_edit, new object[1] { val2 });
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogInfo((object)("Asked the server to set " + e.Definition.Section + "/" + e.Definition.Key));
				}
			}
		}

		private void OnEdit(ZRpc rpc, ZPackage pkg)
		{
			try
			{
				if (!IsServer)
				{
					return;
				}
				ISocket socket = rpc.GetSocket();
				string text = ((socket != null) ? socket.GetHostName() : null) ?? "";
				ZNetPeer val = ((IEnumerable<ZNetPeer>)ZNet.instance.GetPeers()).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer p) => p.m_rpc == rpc));
				string section = pkg.ReadString();
				string key = pkg.ReadString();
				string text2 = pkg.ReadString();
				if (!ZNet.instance.IsAdmin(text))
				{
					ManualLogSource log = _log;
					if (log != null)
					{
						log.LogWarning((object)((val?.m_playerName ?? text) + " tried to change " + section + "/" + key + " but isn't on the admin list"));
					}
					if (_entries.Count > 0)
					{
						rpc.Invoke(_dSend, new object[1] { Values() });
					}
					return;
				}
				ConfigEntryBase val2 = ((IEnumerable<ConfigEntryBase>)_entries).FirstOrDefault((Func<ConfigEntryBase, bool>)((ConfigEntryBase x) => x.Definition.Section == section && x.Definition.Key == key));
				if (val2 != null)
				{
					val2.BoxedValue = TomlTypeConverter.ConvertToValue(text2, val2.SettingType);
					ManualLogSource log2 = _log;
					if (log2 != null)
					{
						log2.LogInfo((object)("Admin " + (val?.m_playerName ?? text) + " set " + section + "/" + key + " = " + text2));
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log3 = _log;
				if (log3 != null)
				{
					log3.LogWarning((object)("Could not take an admin's change: " + ex.Message));
				}
			}
		}

		private void ShowReadOnly()
		{
			bool flag = FromServer && !CanEditServer;
			if (flag == _readOnlyShown)
			{
				return;
			}
			_readOnlyShown = flag;
			foreach (ConfigEntryBase entry in _entries)
			{
				ConfigDescription description = entry.Description;
				object[] array = ((description != null) ? description.Tags : null) ?? new object[0];
				foreach (object obj in array)
				{
					if (IsManagerAttributes(obj))
					{
						try
						{
							AccessTools.Field(obj.GetType(), "ReadOnly")?.SetValue(obj, flag ? ((object)true) : null);
						}
						catch (Exception)
						{
						}
					}
				}
			}
		}

		private static bool IsManagerAttributes(object tag)
		{
			if (tag != null)
			{
				return tag.GetType().Name == "ConfigurationManagerAttributes";
			}
			return false;
		}

		private string OwnText(SyncedText t)
		{
			try
			{
				return t.Own() ?? "";
			}
			catch (Exception ex)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogWarning((object)("Could not read " + t.Key + ": " + ex.Message));
				}
				return t.Given ?? "";
			}
		}

		private void Give(SyncedText t, string text)
		{
			if (t.Given != null && t.Given == text)
			{
				return;
			}
			t.Given = text;
			try
			{
				t.Use(text);
			}
			catch (Exception ex)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogWarning((object)("Could not use " + t.Key + ": " + ex));
				}
			}
		}

		private void RestoreTexts(bool give)
		{
			foreach (SyncedText text in _texts)
			{
				text.Incoming = null;
				if (!text.FromServer)
				{
					continue;
				}
				text.FromServer = false;
				if (give)
				{
					Give(text, OwnText(text));
					ManualLogSource log = _log;
					if (log != null)
					{
						log.LogInfo((object)("Your own " + text.Key + " applies again"));
					}
				}
			}
		}

		private void AskTexts(ZRpc server)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Expected O, but got Unknown
			if (_texts.Count == 0 || server == null)
			{
				return;
			}
			ZPackage val = new ZPackage();
			val.Write(_texts.Count);
			foreach (SyncedText text in _texts)
			{
				val.Write(text.Key);
			}
			server.Invoke(_dTextAsk, new object[1] { val });
		}

		private void OnTextAsk(ZRpc rpc, ZPackage pkg)
		{
			try
			{
				if (!IsServer)
				{
					return;
				}
				int num = pkg.ReadInt();
				HashSet<string> hashSet = new HashSet<string>();
				for (int i = 0; i < num && i < 1000; i++)
				{
					string key = pkg.ReadString();
					if (_texts.Any((SyncedText x) => x.Key == key))
					{
						hashSet.Add(key);
					}
				}
				if (_textPeers.TryGetValue(rpc, out var value))
				{
					hashSet.UnionWith(value);
				}
				foreach (ZRpc item in _textPeers.Keys.Where((ZRpc r) => !r.IsConnected()).ToList())
				{
					_textPeers.Remove(item);
				}
				_textPeers[rpc] = hashSet;
				foreach (SyncedText text in _texts)
				{
					if (hashSet.Contains(text.Key))
					{
						SendText(rpc, text);
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogWarning((object)("Could not answer a player's ask for server data: " + ex.Message));
				}
			}
		}

		private void SendTexts()
		{
			_textPending = false;
			_nextTextSend = Time.unscaledTime + 0.5f;
			if (!IsServer)
			{
				return;
			}
			foreach (SyncedText text in _texts)
			{
				if (!text.Pending)
				{
					continue;
				}
				text.Pending = false;
				foreach (ZNetPeer peer in ZNet.instance.GetPeers())
				{
					if (peer?.m_rpc != null && _textPeers.TryGetValue(peer.m_rpc, out var value) && value.Contains(text.Key))
					{
						SendText(peer.m_rpc, text);
					}
				}
			}
		}

		private void SendText(ZRpc rpc, SyncedText t)
		{
			byte[] array = Packed(t);
			if (array != null)
			{
				_outbox.RemoveAll((Sending s) => s.Rpc == rpc && s.Key == t.Key);
				_outbox.Add(new Sending
				{
					Rpc = rpc,
					Key = t.Key,
					Data = array,
					Parts = Math.Max(1, (array.Length + 65536 - 1) / 65536)
				});
				Pump();
			}
		}

		private void Pump()
		{
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Expected O, but got Unknown
			for (int num = _outbox.Count - 1; num >= 0; num--)
			{
				Sending sending = _outbox[num];
				try
				{
					while (sending.Next < sending.Parts && sending.Rpc.IsConnected() && sending.Rpc.GetSocket().GetSendQueueSize() < 10240)
					{
						int num2 = sending.Next * 65536;
						int num3 = Math.Min(65536, sending.Data.Length - num2);
						byte[] array = new byte[num3];
						Buffer.BlockCopy(sending.Data, num2, array, 0, num3);
						ZPackage val = new ZPackage();
						val.Write(sending.Key);
						val.Write(sending.Next);
						val.Write(sending.Parts);
						val.Write(array);
						sending.Rpc.Invoke(_dText, new object[1] { val });
						sending.Next++;
					}
					if (sending.Next >= sending.Parts || !sending.Rpc.IsConnected())
					{
						_outbox.RemoveAt(num);
					}
				}
				catch (Exception ex)
				{
					_outbox.RemoveAt(num);
					ManualLogSource log = _log;
					if (log != null)
					{
						log.LogWarning((object)("Could not send " + sending.Key + " to a player: " + ex.Message));
					}
				}
			}
		}

		private byte[] Packed(SyncedText t)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Expected O, but got Unknown
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			if (t.Packed == null)
			{
				string text = OwnText(t);
				ZPackage val = new ZPackage();
				val.Write(text);
				ZPackage val2 = new ZPackage();
				bool flag = text.Length > 1024;
				val2.Write(flag);
				if (flag)
				{
					val2.WriteCompressed(val);
				}
				else
				{
					val2.Write(val);
				}
				t.Packed = val2.GetArray();
				t.TooBig = t.Packed.Length > 8388608;
				string arg = $"{t.Packed.Length / 1024} KB" + (flag ? $" gzipped ({text.Length / 1024} KB of text)" : "");
				if (t.TooBig)
				{
					ManualLogSource log = _log;
					if (log != null)
					{
						log.LogError((object)$"{t.Key} is too big to send to players ({arg}, at most {8} MB): they keep their own.");
					}
				}
				else if (t.Packed.Length > 262144)
				{
					ManualLogSource log2 = _log;
					if (log2 != null)
					{
						log2.LogWarning((object)$"{t.Key} is big to send to every player who joins ({arg}): it takes about {(float)t.Packed.Length / 153600f:0} s of their connection, and their world loads slower meanwhile.");
					}
				}
			}
			if (!t.TooBig)
			{
				return t.Packed;
			}
			return null;
		}

		private void OnText(ZRpc rpc, ZPackage pkg)
		{
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Expected O, but got Unknown
			if (!IsServerConnection(rpc))
			{
				return;
			}
			SyncedText syncedText = null;
			try
			{
				string key = pkg.ReadString();
				int num = pkg.ReadInt();
				int num2 = pkg.ReadInt();
				byte[] array = pkg.ReadByteArray();
				syncedText = _texts.FirstOrDefault((SyncedText x) => x.Key == key);
				if (syncedText == null)
				{
					return;
				}
				if (num2 < 1 || num2 > 129 || num < 0 || num >= num2)
				{
					throw new InvalidDataException($"part {num} of {num2}");
				}
				if (num == 0)
				{
					syncedText.Incoming = new MemoryStream();
					syncedText.NextPart = 0;
				}
				if (syncedText.Incoming == null || num != syncedText.NextPart)
				{
					throw new InvalidDataException($"part {num} came out of order");
				}
				syncedText.Incoming.Write(array, 0, array.Length);
				syncedText.NextPart = num + 1;
				if (num >= num2 - 1)
				{
					ZPackage val = new ZPackage(syncedText.Incoming.ToArray());
					syncedText.Incoming = null;
					ZPackage val2 = (val.ReadBool() ? val.ReadCompressedPackage() : val.ReadPackage());
					string text = val2.ReadString();
					syncedText.FromServer = true;
					Give(syncedText, text);
					ManualLogSource log = _log;
					if (log != null)
					{
						log.LogInfo((object)$"The server's {key} applies while you play here ({text.Length / 1024} KB)");
					}
				}
			}
			catch (Exception ex)
			{
				if (syncedText != null)
				{
					syncedText.Incoming = null;
				}
				ManualLogSource log2 = _log;
				if (log2 != null)
				{
					log2.LogWarning((object)("Could not read the server's data" + ((syncedText != null) ? (" for " + syncedText.Key) : "") + ": " + ex.Message));
				}
			}
		}

		private void SetServer(ServerMod state, string version, int protocol)
		{
			if (state != ServerMod.Checking)
			{
				_askedAt = -1f;
			}
			bool flag = state != Server || version != ServerVersion;
			Server = state;
			ServerVersion = version;
			_serverProtocol = protocol;
			if (!flag)
			{
				return;
			}
			Guard();
			try
			{
				this.ServerChanged?.Invoke();
			}
			catch (Exception ex)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogWarning((object)("ServerChanged: " + ex));
				}
			}
		}

		private void Guard()
		{
			bool flag = Server == ServerMod.NotConnected || Server == ServerMod.Installed;
			bool flag2 = false;
			foreach (GameObject guardedPiece in _guardedPieces)
			{
				Piece val = (((Object)(object)guardedPiece != (Object)null) ? guardedPiece.GetComponent<Piece>() : null);
				if ((Object)(object)val != (Object)null && val.m_enabled != flag)
				{
					val.m_enabled = flag;
					flag2 = true;
				}
			}
			foreach (Recipe guardedRecipe in _guardedRecipes)
			{
				if ((Object)(object)guardedRecipe != (Object)null && guardedRecipe.m_enabled != flag)
				{
					guardedRecipe.m_enabled = flag;
					flag2 = true;
				}
			}
			if (flag2 && (Object)(object)Player.m_localPlayer != (Object)null)
			{
				try
				{
					UpdateAvailablePieces?.Invoke(Player.m_localPlayer, null);
				}
				catch (Exception)
				{
				}
			}
		}

		private void WatchFile()
		{
			try
			{
				string configFilePath = _config.ConfigFilePath;
				_watcher = new FileSystemWatcher(Path.GetDirectoryName(configFilePath), Path.GetFileName(configFilePath))
				{
					NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite)
				};
				FileSystemEventHandler value = delegate
				{
					_fileTouched = true;
				};
				_watcher.Changed += value;
				_watcher.Created += value;
				_watcher.Renamed += delegate
				{
					_fileTouched = true;
				};
				_watcher.EnableRaisingEvents = true;
			}
			catch (Exception ex)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogWarning((object)("Edits to the config file apply after a restart (can't watch it: " + ex.Message + ")"));
				}
			}
		}

		private void ReloadFile()
		{
			string text = ReadFile(_config.ConfigFilePath);
			if (text == null)
			{
				if (File.Exists(_config.ConfigFilePath))
				{
					_reloadAt = Time.unscaledTime + 1f;
				}
			}
			else
			{
				if (text == _fileText)
				{
					return;
				}
				_fileText = text;
				bool saveOnConfigSet = _config.SaveOnConfigSet;
				_reloading = true;
				try
				{
					_config.SaveOnConfigSet = false;
					_config.Reload();
				}
				catch (Exception ex)
				{
					ManualLogSource log = _log;
					if (log != null)
					{
						log.LogWarning((object)("Could not read the edited config file (check the line you changed): " + ex.Message));
					}
				}
				finally
				{
					_config.SaveOnConfigSet = saveOnConfigSet;
					_reloading = false;
				}
			}
		}

		private void PatchSave()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Expected O, but got Unknown
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Expected O, but got Unknown
			//IL_0067: Expected O, but got Unknown
			try
			{
				_saveHarmony = new Harmony(_send + ".save");
				_saveHarmony.Patch((MethodBase)AccessTools.Method(typeof(ConfigFile), "Save", (Type[])null, (Type[])null), new HarmonyMethod(typeof(ServerSettings), "BeforeSave", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(ServerSettings), "AfterSave", (Type[])null), (HarmonyMethod)null);
			}
			catch (Exception ex)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogWarning((object)("Could not keep the server's values out of your config file: " + ex.Message));
				}
			}
		}

		private static void UnpatchSave()
		{
			try
			{
				Harmony saveHarmony = _saveHarmony;
				if (saveHarmony != null)
				{
					saveHarmony.UnpatchSelf();
				}
			}
			catch (Exception)
			{
			}
			_saveHarmony = null;
		}

		private static void BeforeSave(ConfigFile __instance)
		{
			foreach (ServerSettings item in Live)
			{
				if (item._config != __instance)
				{
					continue;
				}
				foreach (KeyValuePair<ConfigEntryBase, object> item2 in item._own)
				{
					SetQuietly(item2.Key, item2.Value);
				}
			}
		}

		private static Exception AfterSave(ConfigFile __instance, Exception __exception)
		{
			foreach (ServerSettings item in Live)
			{
				if (item._config != __instance)
				{
					continue;
				}
				foreach (KeyValuePair<ConfigEntryBase, object> item2 in item._server)
				{
					SetQuietly(item2.Key, item2.Value);
				}
				item._fileText = ReadFile(__instance.ConfigFilePath);
			}
			return __exception;
		}

		private static string ReadFile(string path)
		{
			try
			{
				return File.Exists(path) ? File.ReadAllText(path) : null;
			}
			catch (Exception)
			{
				return null;
			}
		}

		private static void SetQuietly(ConfigEntryBase e, object value)
		{
			AccessTools.Field(((object)e).GetType(), "_typedValue")?.SetValue(e, value);
		}

		private static BepInPlugin PluginOf(string guid)
		{
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Expected O, but got Unknown
			Type[] array;
			try
			{
				array = typeof(ServerSettings).Assembly.GetTypes();
			}
			catch (ReflectionTypeLoadException ex)
			{
				array = ex.Types.Where((Type t) => t != null).ToArray();
			}
			Type[] array2 = array;
			foreach (Type type in array2)
			{
				object[] customAttributes = type.GetCustomAttributes(typeof(BepInPlugin), inherit: false);
				for (int num2 = 0; num2 < customAttributes.Length; num2++)
				{
					BepInPlugin val = (BepInPlugin)customAttributes[num2];
					if (val.GUID == guid || guid.StartsWith(val.GUID + "."))
					{
						return val;
					}
				}
			}
			return null;
		}

		private void Unsubscribe(ConfigEntryBase e)
		{
			((object)e).GetType().GetEvent("SettingChanged")?.RemoveEventHandler(e, new EventHandler(OnChanged));
		}
	}
}