Decompiled source of Modzified Skill Farming v1.0.0

plugins/Modzified_Skill_Farming.dll

Decompiled 2 weeks 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.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
using ServerSync;
using TMPro;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyMetadata("AI_Assisted_Creation", "true")]
[assembly: AssemblyCompany("Modzified_Skill_Farming")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+e021fb08c1d911983c48390996e73faf517de7d6")]
[assembly: AssemblyProduct("Modzified_Skill_Farming")]
[assembly: AssemblyTitle("Modzified_Skill_Farming")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 Modzified_Skill_Farming
{
	internal static class BiomeFree
	{
		internal sealed class NativeRules
		{
			internal Biome Biome;

			internal bool Heat;

			internal bool Cold;
		}

		internal static readonly Dictionary<int, NativeRules> Native = new Dictionary<int, NativeRules>();

		internal static bool Eligible(float plantedLevel)
		{
			int anywhereLevel = Settings.AnywhereLevel;
			if (anywhereLevel < 0 || plantedLevel < 0f)
			{
				return false;
			}
			return plantedLevel >= Mathf.Min((float)anywhereLevel, FarmingCap.Value);
		}

		internal static void Apply(Plant plant)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: 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_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			if (Object.op_Implicit((Object)(object)plant.m_nview) && plant.m_nview.IsValid())
			{
				ZDO zDO = plant.m_nview.GetZDO();
				int prefab = zDO.GetPrefab();
				if (!Native.TryGetValue(prefab, out NativeRules value))
				{
					value = new NativeRules
					{
						Biome = plant.m_biome,
						Heat = plant.m_tolerateHeat,
						Cold = plant.m_tolerateCold
					};
					Native[prefab] = value;
				}
				bool flag = Eligible(PlanterLevel.Read(zDO));
				plant.m_biome = (Biome)(flag ? (value.Biome | 0x37F) : value.Biome);
				plant.m_tolerateHeat = flag || value.Heat;
				plant.m_tolerateCold = flag || value.Cold;
			}
		}

		internal static void Sweep()
		{
			List<SlowUpdate> allInstaces = SlowUpdate.GetAllInstaces();
			int num = 0;
			for (int i = 0; i < allInstaces.Count; i++)
			{
				SlowUpdate obj = allInstaces[i];
				Plant val = (Plant)(object)((obj is Plant) ? obj : null);
				if (val != null)
				{
					Apply(val);
					num++;
				}
			}
			if (Settings.DebugOn)
			{
				SkillFarmingPlugin.LogAt((LogLevel)32, string.Format("{0}: biome sweep touched {1} plants.", "Modzified_Skill_Farming", num));
			}
		}
	}
	internal static class Coexistence
	{
		private const string SmoothbrainGuid = "org.bepinex.plugins.farming";

		private static bool _logged;

		internal static void WarnOnce()
		{
			if (_logged)
			{
				return;
			}
			_logged = true;
			foreach (KeyValuePair<string, PluginInfo> pluginInfo in Chainloader.PluginInfos)
			{
				if (string.Equals(pluginInfo.Key, "org.bepinex.plugins.farming", StringComparison.OrdinalIgnoreCase))
				{
					SkillFarmingPlugin.LogAt((LogLevel)4, "Modzified_Skill_Farming: Farming by Smoothbrain is also loaded. Both mods change growth, biome rules, stamina and harvest. Results can differ from the settings.");
					break;
				}
			}
		}
	}
	internal static class GrowthSpeed
	{
		private static int _calls;

		private static float _windowStart;

		internal static void Adjust(Plant plant, ref float growTime)
		{
			if (Settings.DebugOn)
			{
				Count();
			}
			if (Settings.GrowthActive && Object.op_Implicit((Object)(object)plant.m_nview) && plant.m_nview.IsValid())
			{
				float num = PlanterLevel.Read(plant.m_nview.GetZDO());
				if (!(num < 0f))
				{
					float num2 = PlanterLevel.Curve(Settings.GrowthMin, Settings.GrowthMax, num) / 100f;
					growTime *= 1f - Mathf.Clamp(num2, 0f, 0.9f);
				}
			}
		}

		private static void Count()
		{
			_calls++;
			float unscaledTime = Time.unscaledTime;
			if (!(unscaledTime - _windowStart < 5f))
			{
				SkillFarmingPlugin.LogAt((LogLevel)32, string.Format("{0}: GetGrowTime ran {1} times in the last {2:0.0}s.", "Modzified_Skill_Farming", _calls, unscaledTime - _windowStart));
				_calls = 0;
				_windowStart = unscaledTime;
			}
		}
	}
	internal static class PlantingStamina
	{
		internal static float Factor(Player player, SkillType skill)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Invalid comparison between Unknown and I4
			if (!PlanterLevel.JustPlacedPlant || (int)skill != 106)
			{
				return ((Character)player).GetSkillFactor(skill);
			}
			PlanterLevel.JustPlacedPlant = false;
			return Cut(player) / 50f;
		}

		internal static float Cut(Player player)
		{
			return Mathf.Clamp(PlanterLevel.Curve(Settings.StaminaMin, Settings.StaminaMax, PlanterLevel.RawLevel(player)), 0f, 100f);
		}
	}
	internal static class ScytheStamina
	{
		private const float VanillaWeight = 33f;

		internal static float Factor(Character character, SkillType skill, Attack attack)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			if (attack.m_harvest)
			{
				Player val = (Player)(object)((character is Player) ? character : null);
				if (val != null)
				{
					return Mathf.Clamp(PlanterLevel.Curve(Settings.ScytheStaminaMin, Settings.ScytheStaminaMax, PlanterLevel.RawLevel(val)), 0f, 100f) / 33f;
				}
			}
			return character.GetSkillFactor(skill);
		}
	}
	internal static class BountifulHarvest
	{
		internal static float Factor(Player player, SkillType skill, Pickable pickable)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Invalid comparison between Unknown and I4
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			float maxLevelBonusChance = pickable.m_maxLevelBonusChance;
			if ((int)skill != 106 || maxLevelBonusChance <= 0f)
			{
				return ((Character)player).GetSkillFactor(skill);
			}
			return PlanterLevel.Curve(Settings.BonusMin, Settings.BonusMax, PlanterLevel.RawLevel(player)) / 100f / maxLevelBonusChance;
		}
	}
	internal static class WiderSweep
	{
		internal static float Radius(float near, float far, float factor)
		{
			float num = Mathf.Lerp(near, far, factor);
			if (!Settings.SweepActive || !Object.op_Implicit((Object)(object)Player.m_localPlayer))
			{
				return num;
			}
			float num2 = PlanterLevel.Curve(Settings.SweepMin, Settings.SweepMax, PlanterLevel.RawLevel(Player.m_localPlayer)) / 100f;
			return num * (1f + Mathf.Max(num2, 0f));
		}
	}
	internal static class FarmingCap
	{
		internal const float Fallback = 100f;

		private static Func<SkillType, float>? _getCap;

		private static bool _probed;

		private static float _cached = 100f;

		private static float _readAt = -10f;

		internal static float Value
		{
			get
			{
				float unscaledTime = Time.unscaledTime;
				if (unscaledTime - _readAt < 1f)
				{
					return _cached;
				}
				_readAt = unscaledTime;
				_cached = Read();
				return _cached;
			}
		}

		internal static void Probe()
		{
			if (_probed)
			{
				return;
			}
			_probed = true;
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			for (int i = 0; i < assemblies.Length; i++)
			{
				Type type = assemblies[i].GetType("SkillLimitExtender.SkillConfigManager");
				if (!(type == null))
				{
					Type[] types = new Type[1] { typeof(SkillType) };
					MethodInfo methodInfo = type.GetMethod("GetCap", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, types, null) ?? type.GetMethod("GetSkillLimit", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, types, null);
					if (methodInfo != null)
					{
						_getCap = Bind(methodInfo);
					}
					break;
				}
			}
		}

		private static Func<SkillType, float>? Bind(MethodInfo method)
		{
			try
			{
				if (method.ReturnType == typeof(float))
				{
					return (Func<SkillType, float>)Delegate.CreateDelegate(typeof(Func<SkillType, float>), method);
				}
				if (method.ReturnType == typeof(int))
				{
					Func<SkillType, int> asInt = (Func<SkillType, int>)Delegate.CreateDelegate(typeof(Func<SkillType, int>), method);
					return (SkillType skill) => asInt(skill);
				}
			}
			catch (Exception ex)
			{
				SkillFarmingPlugin.LogAt((LogLevel)4, string.Format("{0}: could not bind the live skill cap ({1}). Using {2}.", "Modzified_Skill_Farming", ex.GetType().Name, 100f));
			}
			return null;
		}

		private static float Read()
		{
			Probe();
			if (_getCap == null)
			{
				return 100f;
			}
			try
			{
				float num = _getCap((SkillType)106);
				return (num > 0f) ? num : 100f;
			}
			catch (Exception)
			{
				return 100f;
			}
		}
	}
	internal static class GrowChance
	{
		internal static float Percent(float plantedLevel, bool outsideBiome)
		{
			float num = PlanterLevel.Curve(Settings.ChanceMin, Settings.ChanceMax, plantedLevel);
			if (outsideBiome)
			{
				num -= Settings.Penalty;
			}
			return Mathf.Clamp(num, 0f, 100f);
		}

		internal static bool OutsideBiome(Plant plant, ZDO zdo)
		{
			//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)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Invalid comparison between Unknown and I4
			if (Settings.Penalty <= 0f || !BiomeFree.Native.TryGetValue(zdo.GetPrefab(), out BiomeFree.NativeRules value))
			{
				return false;
			}
			Vector3 position = ((Component)plant).transform.position;
			Heightmap val = Heightmap.FindHeightmap(position);
			if (!Object.op_Implicit((Object)(object)val))
			{
				return false;
			}
			return (val.GetBiome(position, 0.02f, false) & value.Biome) == 0;
		}

		internal static float Roll(int seed)
		{
			int num = seed ^ -1640531527;
			int num2 = (num ^ (num >>> 16)) * 2146121005;
			int num3 = (num2 ^ (num2 >>> 15)) * -2073254261;
			return (float)((uint)(num3 ^ (num3 >>> 16)) % 10000u) / 100f;
		}
	}
	internal static class PlanterLevel
	{
		private const string KeyName = "modzified_skill_farming_level";

		private static readonly int Key = StringExtensionMethods.GetStableHashCode("modzified_skill_farming_level");

		internal static float Pending = -1f;

		internal static bool JustPlacedPlant;

		internal static float Read(ZDO zdo)
		{
			return zdo.GetFloat(Key, -1f);
		}

		internal static void Write(ZDO zdo, float level)
		{
			zdo.Set(Key, level);
		}

		internal static float RawLevel(Player player)
		{
			return ((Character)player).GetSkills().GetSkillLevel((SkillType)106);
		}

		internal static float Ratio(float level)
		{
			return Mathf.Clamp01(level / FarmingCap.Value);
		}

		internal static float Curve(float min, float max, float level)
		{
			return min + (max - min) * Ratio(level);
		}
	}
	internal static class PlantHover
	{
		private const string Yellow = "yellow";

		private const string Green = "green";

		private static Plant? _plant;

		private static int _second = -2;

		private static int _version = -1;

		private static Status _status;

		private static string _text = "";

		internal static string Extra(Plant plant)
		{
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: 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_0093: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)plant.m_nview) || !plant.m_nview.IsValid())
			{
				return "";
			}
			ZDO zDO = plant.m_nview.GetZDO();
			float num = PlanterLevel.Read(zDO);
			if (num < 0f)
			{
				return "";
			}
			Status status = plant.GetStatus();
			int num2 = -1;
			if ((int)status == 0)
			{
				double num3 = (double)plant.GetGrowTime() - plant.TimeSincePlanted();
				num2 = ((num3 > 0.0) ? ((int)num3) : (-1));
			}
			if ((Object)(object)plant == (Object)(object)_plant && num2 == _second && Settings.Version == _version && status == _status)
			{
				return _text;
			}
			_plant = plant;
			_second = num2;
			_version = Settings.Version;
			_status = status;
			_text = Build(plant, zDO, num, num2);
			return _text;
		}

		private static string Build(Plant plant, ZDO zdo, float level, int seconds)
		{
			bool flag = GrowChance.OutsideBiome(plant, zdo);
			float num = GrowChance.Percent(level, flag);
			StringBuilder stringBuilder = new StringBuilder("\nGrow Chance: <color=");
			stringBuilder.Append((num >= 75f) ? "green" : "yellow").Append('>').Append(num.ToString("0.##"))
				.Append("%</color>");
			if (flag && Settings.Penalty > 0f)
			{
				stringBuilder.Append(" ( -").Append(Settings.Penalty.ToString("0.##")).Append("% Biome penalty )");
			}
			if (seconds >= 0)
			{
				stringBuilder.Append("\nFully grown in ").Append(Format(seconds));
			}
			return stringBuilder.ToString();
		}

		private static string Format(int seconds)
		{
			int num = seconds / 3600;
			int num2 = seconds % 3600 / 60;
			int num3 = seconds % 60;
			if (num > 0)
			{
				return $"{num}h {num2}m {num3}s";
			}
			if (num2 <= 0)
			{
				return $"{num3}s";
			}
			return $"{num2}m {num3}s";
		}
	}
	[BepInPlugin("modzified_skill_farming", "Modzified_Skill_Farming", "1.0.0")]
	public class SkillFarmingPlugin : BaseUnityPlugin
	{
		internal const string ModName = "Modzified_Skill_Farming";

		internal const string ModVersion = "1.0.0";

		internal const string ModGUID = "modzified_skill_farming";

		private readonly Harmony _harmony = new Harmony("modzified_skill_farming");

		internal static ManualLogSource Log { get; private set; }

		internal static bool Allows(LogLevel level)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Invalid comparison between Unknown and I4
			if (Settings.LogLevels != null)
			{
				return (Settings.LogLevels.Value & level) > 0;
			}
			return true;
		}

		internal static void LogAt(LogLevel level, string message)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			if (Allows(level))
			{
				Log.Log(level, (object)message);
			}
		}

		private void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
			Settings.Init(((BaseUnityPlugin)this).Config);
			_harmony.PatchAll(Assembly.GetExecutingAssembly());
			LogAt((LogLevel)16, "Modzified_Skill_Farming v1.0.0 loaded (GUID modzified_skill_farming).");
		}

		private void Start()
		{
			FarmingCap.Probe();
			Coexistence.WarnOnce();
		}

		private void OnDestroy()
		{
			_harmony.UnpatchSelf();
		}
	}
	internal static class ScytheReach
	{
		private static readonly HashSet<Pickable> Done = new HashSet<Pickable>();

		private static readonly HashSet<int> Logged = new HashSet<int>();

		private static int _frame = -1;

		internal static Pickable? Resolve(GameObject target)
		{
			Pickable val = target.GetComponent<Pickable>();
			if (Settings.ScytheAllowHashes.Count == 0)
			{
				return val;
			}
			if ((Object)(object)val != (Object)null && val.m_harvestable)
			{
				return val;
			}
			bool flag = (Object)(object)val == (Object)null;
			if (flag)
			{
				val = target.GetComponentInParent<Pickable>();
			}
			if ((Object)(object)val == (Object)null || !Object.op_Implicit((Object)(object)val.m_nview) || !val.m_nview.IsValid())
			{
				if (!flag)
				{
					return val;
				}
				return null;
			}
			int prefab = val.m_nview.GetZDO().GetPrefab();
			if (!Settings.ScytheAllowHashes.Contains(prefab))
			{
				if (!flag)
				{
					return val;
				}
				return null;
			}
			if (_frame != Time.frameCount)
			{
				_frame = Time.frameCount;
				Done.Clear();
			}
			if (!Done.Add(val))
			{
				return null;
			}
			val.m_harvestable = true;
			if (Settings.DebugOn && Logged.Add(prefab))
			{
				SkillFarmingPlugin.LogAt((LogLevel)32, string.Format("{0}: Scythe reached {1} (child collider {2}).", "Modzified_Skill_Farming", ((Object)val).name, flag));
			}
			return val;
		}
	}
	internal static class Settings
	{
		internal const string SectionCultivator = "1. Cultivator";

		internal const string SectionScythe = "2. Scythe";

		internal const string SectionBonusYield = "3. Bonus Yield";

		internal const string SectionLogging = "9. Logging";

		internal const LogLevel DefaultLogLevels = (LogLevel)31;

		internal static HashSet<int> ScytheAllowHashes = new HashSet<int>();

		internal static int AnywhereLevel = -1;

		internal static float StaminaMin;

		internal static float StaminaMax;

		internal static float ScytheStaminaMin;

		internal static float ScytheStaminaMax = 33f;

		internal static float ChanceMin = 50f;

		internal static float ChanceMax = 100f;

		internal static float Penalty = 20f;

		internal static float GrowthMin;

		internal static float GrowthMax;

		internal static float BonusMin;

		internal static float BonusMax = 25f;

		internal static float SweepMin;

		internal static float SweepMax;

		internal static bool GrowthActive;

		internal static bool SweepActive;

		internal static bool DebugOn;

		internal static int Version;

		internal static ConfigSync Sync { get; private set; } = null;

		internal static ConfigEntry<LogLevel> LogLevels { get; private set; } = null;

		internal static ConfigEntry<float> GrowChanceAt0 { get; private set; } = null;

		internal static ConfigEntry<float> GrowChanceAtMax { get; private set; } = null;

		internal static ConfigEntry<float> GrowthCutAt0 { get; private set; } = null;

		internal static ConfigEntry<float> GrowthCutAtMax { get; private set; } = null;

		internal static ConfigEntry<float> CultivatorStaminaAt0 { get; private set; } = null;

		internal static ConfigEntry<float> CultivatorStaminaAtMax { get; private set; } = null;

		internal static ConfigEntry<int> PlantAnywhereLevel { get; private set; } = null;

		internal static ConfigEntry<float> BiomePenalty { get; private set; } = null;

		internal static ConfigEntry<float> SweepAt0 { get; private set; } = null;

		internal static ConfigEntry<float> SweepAtMax { get; private set; } = null;

		internal static ConfigEntry<float> ScytheStaminaAt0 { get; private set; } = null;

		internal static ConfigEntry<float> ScytheStaminaAtMax { get; private set; } = null;

		internal static ConfigEntry<string> ScytheAllow { get; private set; } = null;

		internal static ConfigEntry<float> BonusChanceAt0 { get; private set; } = null;

		internal static ConfigEntry<float> BonusChanceAtMax { get; private set; } = null;

		internal static void Init(ConfigFile config)
		{
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Expected O, but got Unknown
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Expected O, but got Unknown
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_013f: Expected O, but got Unknown
			//IL_0180: Unknown result type (might be due to invalid IL or missing references)
			//IL_018a: Expected O, but got Unknown
			//IL_01cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d5: Expected O, but got Unknown
			//IL_0216: Unknown result type (might be due to invalid IL or missing references)
			//IL_0220: Expected O, but got Unknown
			//IL_025b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0265: Expected O, but got Unknown
			//IL_02a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b0: Expected O, but got Unknown
			//IL_02f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fb: Expected O, but got Unknown
			//IL_033c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0346: Expected O, but got Unknown
			//IL_0387: Unknown result type (might be due to invalid IL or missing references)
			//IL_0391: Expected O, but got Unknown
			//IL_03d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_03dc: Expected O, but got Unknown
			//IL_0411: Unknown result type (might be due to invalid IL or missing references)
			//IL_041b: Expected O, but got Unknown
			//IL_045c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0466: Expected O, but got Unknown
			//IL_04a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_04b1: Expected O, but got Unknown
			//IL_04e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_04ed: Expected O, but got Unknown
			Sync = new ConfigSync("modzified_skill_farming")
			{
				DisplayName = "Modzified_Skill_Farming",
				CurrentVersion = "1.0.0",
				ModRequired = false,
				IsLocked = true
			};
			AcceptableValueRange<float> val = new AcceptableValueRange<float>(0f, 100f);
			AcceptableValueRange<float> val2 = new AcceptableValueRange<float>(0f, 90f);
			AcceptableValueRange<float> val3 = new AcceptableValueRange<float>(0f, 50f);
			GrowChanceAt0 = BindSynced(config, "1. Cultivator", "Grow chance at skill 0", 50f, new ConfigDescription("Plant grow chance % at Farming skill 0. [Server Synced]", (AcceptableValueBase)(object)val, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 8,
					ShowRangeAsPercent = false
				}
			}));
			GrowChanceAtMax = BindSynced(config, "1. Cultivator", "Grow chance at skill max", 100f, new ConfigDescription("Plant grow chance % at Farming skill max. [Server Synced]", (AcceptableValueBase)(object)val, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 7,
					ShowRangeAsPercent = false
				}
			}));
			GrowthCutAt0 = BindSynced(config, "1. Cultivator", "Growth time reduction at skill 0", 0f, new ConfigDescription("Growth time reduction % at Farming skill 0. [Server Synced]", (AcceptableValueBase)(object)val2, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 6,
					ShowRangeAsPercent = false
				}
			}));
			GrowthCutAtMax = BindSynced(config, "1. Cultivator", "Growth time reduction at skill max", 50f, new ConfigDescription("Growth time reduction % at Farming skill max. [Server Synced]", (AcceptableValueBase)(object)val2, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 5,
					ShowRangeAsPercent = false
				}
			}));
			CultivatorStaminaAt0 = BindSynced(config, "1. Cultivator", "Cultivator stamina reduction at skill 0", 0f, new ConfigDescription("Stamina reduction % of Cultivator use at Farming skill 0. [Server Synced]", (AcceptableValueBase)(object)val, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 4,
					ShowRangeAsPercent = false
				}
			}));
			CultivatorStaminaAtMax = BindSynced(config, "1. Cultivator", "Cultivator stamina reduction at skill max", 50f, new ConfigDescription("Stamina reduction % of Cultivator use at Farming skill max. [Server Synced]", (AcceptableValueBase)(object)val, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 3,
					ShowRangeAsPercent = false
				}
			}));
			PlantAnywhereLevel = BindSynced(config, "1. Cultivator", "Plant anywhere at skill level", -1, new ConfigDescription("Farming level threshold to allow planting in any biome.\nValue -1 disables this feature. The level is capped at 100 unless a mod raises the skill cap. [Server Synced]", (AcceptableValueBase)(object)new AcceptableValueRange<int>(-1, 250), new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 2
				}
			}));
			BiomePenalty = BindSynced(config, "1. Cultivator", "Out-of-biome grow chance penalty", 20f, new ConfigDescription("Plant grow chance % reduction when a plant grows outside its native biome.\nThis only matters when \"Plant anywhere\" is on. [Server Synced]", (AcceptableValueBase)(object)val3, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 1,
					ShowRangeAsPercent = false
				}
			}));
			SweepAt0 = BindSynced(config, "2. Scythe", "Scythe radius expansion at skill 0", 0f, new ConfigDescription("Harvest radius expansion % of the Scythe at Farming skill 0.\nThe number is a percent on top of the vanilla radius. 0 keeps the vanilla radius. [Server Synced]", (AcceptableValueBase)(object)val, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 4,
					ShowRangeAsPercent = false
				}
			}));
			SweepAtMax = BindSynced(config, "2. Scythe", "Scythe radius expansion at skill max", 0f, new ConfigDescription("Harvest radius expansion % of the Scythe at Farming skill max.\nThe number is a percent on top of the vanilla radius. 0 keeps the vanilla radius. [Server Synced]", (AcceptableValueBase)(object)val, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 3,
					ShowRangeAsPercent = false
				}
			}));
			ScytheStaminaAt0 = BindSynced(config, "2. Scythe", "Scythe stamina reduction at skill 0", 0f, new ConfigDescription("Stamina reduction % of Scythe use at Farming skill 0. [Server Synced]", (AcceptableValueBase)(object)val, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 2,
					ShowRangeAsPercent = false
				}
			}));
			ScytheStaminaAtMax = BindSynced(config, "2. Scythe", "Scythe stamina reduction at skill max", 33f, new ConfigDescription("Stamina reduction % of Scythe use at Farming skill max. [Server Synced]", (AcceptableValueBase)(object)val, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 1,
					ShowRangeAsPercent = false
				}
			}));
			ScytheAllow = BindSynced(config, "2. Scythe", "Allow Scythe to harvest", "Pickable_Mushroom_JotunPuffs, Pickable_Mushroom_Magecap, VineAsh", new ConfigDescription("Prefab names the Scythe can also harvest. Separate names with commas.\nUse it for plants the vanilla Scythe skips, such as Jotun Puffs, Magecap and the vines. An empty list keeps the vanilla behavior. [Server Synced]", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 0
				}
			}));
			BonusChanceAt0 = BindSynced(config, "3. Bonus Yield", "Bonus yield chance at skill 0", 0f, new ConfigDescription("Bonus chance to get one extra yield at Farming skill 0.\nAffects picking and scything. [Server Synced]", (AcceptableValueBase)(object)val, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 2,
					ShowRangeAsPercent = false
				}
			}));
			BonusChanceAtMax = BindSynced(config, "3. Bonus Yield", "Bonus yield chance at skill max", 25f, new ConfigDescription("Bonus chance to get one extra yield at Farming skill max.\nAffects picking and scything. [Server Synced]", (AcceptableValueBase)(object)val, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 1,
					ShowRangeAsPercent = false
				}
			}));
			LogLevels = BindLocal<LogLevel>(config, "9. Logging", "Log levels", (LogLevel)31, new ConfigDescription("Which log levels this mod writes. Add Debug to see patch traces. [Not Server Synced]", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					IsAdvanced = true
				}
			}));
			config.SettingChanged += delegate
			{
				Refresh();
			};
			Refresh();
			WarnInvertedEnds();
		}

		internal static void Refresh()
		{
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			//IL_0139: Invalid comparison between Unknown and I4
			int anywhereLevel = AnywhereLevel;
			AnywhereLevel = PlantAnywhereLevel.Value;
			StaminaMin = CultivatorStaminaAt0.Value;
			StaminaMax = CultivatorStaminaAtMax.Value;
			ScytheStaminaMin = ScytheStaminaAt0.Value;
			ScytheStaminaMax = ScytheStaminaAtMax.Value;
			ChanceMin = GrowChanceAt0.Value;
			ChanceMax = GrowChanceAtMax.Value;
			Penalty = BiomePenalty.Value;
			GrowthMin = GrowthCutAt0.Value;
			GrowthMax = GrowthCutAtMax.Value;
			BonusMin = BonusChanceAt0.Value;
			BonusMax = BonusChanceAtMax.Value;
			SweepMin = SweepAt0.Value;
			SweepMax = SweepAtMax.Value;
			ScytheAllowHashes = ParseNames(ScytheAllow.Value);
			GrowthActive = GrowthMin > 0f || GrowthMax > 0f;
			SweepActive = SweepMin > 0f || SweepMax > 0f;
			DebugOn = (LogLevels.Value & 0x20) > 0;
			Version++;
			if (anywhereLevel != AnywhereLevel)
			{
				BiomeFree.Sweep();
			}
		}

		private static HashSet<int> ParseNames(string list)
		{
			HashSet<int> hashSet = new HashSet<int>();
			string[] array = list.Split(new char[1] { ',' });
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length > 0)
				{
					hashSet.Add(StringExtensionMethods.GetStableHashCode(text));
				}
			}
			return hashSet;
		}

		private static void WarnInvertedEnds()
		{
			Warn(ChanceMin > ChanceMax, "Grow chance at skill 0 is higher than at skill max.");
			Warn(GrowthMin > GrowthMax, "Growth time reduction at skill 0 is higher than at skill max.");
			Warn(BonusMin > BonusMax, "Bonus yield chance at skill 0 is higher than at skill max.");
			Warn(StaminaMin > StaminaMax, "Cultivator stamina reduction at skill 0 is higher than at skill max.");
			Warn(ScytheStaminaMin > ScytheStaminaMax, "Scythe stamina reduction at skill 0 is higher than at skill max.");
			Warn(SweepMin > SweepMax, "Scythe radius expansion at skill 0 is higher than at skill max.");
		}

		private static void Warn(bool inverted, string text)
		{
			if (inverted)
			{
				SkillFarmingPlugin.LogAt((LogLevel)4, "Modzified_Skill_Farming: " + text + " The value falls as skill rises.");
			}
		}

		private static ConfigEntry<T> BindSynced<T>(ConfigFile config, string section, string key, T value, ConfigDescription description)
		{
			ConfigEntry<T> val = config.Bind<T>(section, key, value, description);
			Sync.AddConfigEntry<T>(val).SynchronizedConfig = true;
			return val;
		}

		private static ConfigEntry<T> BindLocal<T>(ConfigFile config, string section, string key, T value, ConfigDescription description)
		{
			ConfigEntry<T> val = config.Bind<T>(section, key, value, description);
			Sync.AddConfigEntry<T>(val).SynchronizedConfig = false;
			return val;
		}
	}
	internal sealed class ConfigurationManagerAttributes
	{
		public int? Order;

		public bool? ShowRangeAsPercent;

		public bool? IsAdvanced;
	}
}
namespace Modzified_Skill_Farming.Patches
{
	[HarmonyPatch(typeof(Plant), "Awake")]
	internal static class Plant_Awake_Patch
	{
		private static void Postfix(Plant __instance)
		{
			if (!Object.op_Implicit((Object)(object)__instance.m_nview))
			{
				return;
			}
			ZDO zDO = __instance.m_nview.GetZDO();
			if (zDO != null)
			{
				if (PlanterLevel.Pending >= 0f && PlanterLevel.Read(zDO) < 0f && zDO.IsOwner())
				{
					PlanterLevel.Write(zDO, PlanterLevel.Pending);
				}
				BiomeFree.Apply(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(Plant), "Grow")]
	internal static class Plant_Grow_Patch
	{
		private static bool Prefix(Plant __instance)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			if ((int)__instance.m_status != 0 || !Object.op_Implicit((Object)(object)__instance.m_nview) || !__instance.m_nview.IsValid())
			{
				return true;
			}
			ZDO zDO = __instance.m_nview.GetZDO();
			float num = PlanterLevel.Read(zDO);
			if (num < 0f)
			{
				return true;
			}
			float num2 = GrowChance.Percent(num, GrowChance.OutsideBiome(__instance, zDO));
			if (num2 >= 100f || GrowChance.Roll(__instance.m_seed) < num2)
			{
				return true;
			}
			if (Settings.DebugOn)
			{
				SkillFarmingPlugin.LogAt((LogLevel)32, string.Format("{0}: {1} failed its grow roll ({2:0.##}%).", "Modzified_Skill_Farming", ((Object)__instance).name, num2));
			}
			__instance.m_nview.Destroy();
			return false;
		}
	}
	[HarmonyPatch(typeof(Plant), "GetGrowTime")]
	internal static class Plant_GetGrowTime_Patch
	{
		private static void Postfix(Plant __instance, ref float __result)
		{
			GrowthSpeed.Adjust(__instance, ref __result);
		}
	}
	[HarmonyPatch(typeof(Plant), "GetHoverText")]
	internal static class Plant_GetHoverText_Patch
	{
		private static void Postfix(Plant __instance, ref string __result)
		{
			if (!string.IsNullOrEmpty(__result))
			{
				__result += PlantHover.Extra(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(Player), "PlacePiece")]
	internal static class Player_PlacePiece_Patch
	{
		private static void Prefix(Player __instance, Piece piece)
		{
			PlanterLevel.Pending = ((Object.op_Implicit((Object)(object)piece) && Object.op_Implicit((Object)(object)((Component)piece).GetComponent<Plant>())) ? PlanterLevel.RawLevel(__instance) : (-1f));
		}

		private static void Finalizer()
		{
			PlanterLevel.Pending = -1f;
		}
	}
	[HarmonyPatch(typeof(Player), "TryPlacePiece")]
	internal static class Player_TryPlacePiece_Patch
	{
		private static void Postfix(Piece piece, bool __result)
		{
			PlanterLevel.JustPlacedPlant = __result && Object.op_Implicit((Object)(object)piece) && Object.op_Implicit((Object)(object)((Component)piece).GetComponent<Plant>());
		}
	}
	[HarmonyPatch(typeof(Player), "GetBuildStamina")]
	internal static class Player_GetBuildStamina_Patch
	{
		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			return TranspilerUtil.ReplaceSkillFactor(instructions, AccessTools.Method(typeof(PlantingStamina), "Factor", (Type[])null, (Type[])null), passInstance: false, "Player.GetBuildStamina");
		}
	}
	[HarmonyPatch(typeof(Attack), "GetAttackStamina")]
	internal static class Attack_GetAttackStamina_Patch
	{
		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			return TranspilerUtil.ReplaceSkillFactor(instructions, AccessTools.Method(typeof(ScytheStamina), "Factor", (Type[])null, (Type[])null), passInstance: true, "Attack.GetAttackStamina");
		}
	}
	[HarmonyPatch(typeof(Pickable), "Interact")]
	internal static class Pickable_Interact_Patch
	{
		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			return TranspilerUtil.ReplaceSkillFactor(instructions, AccessTools.Method(typeof(BountifulHarvest), "Factor", (Type[])null, (Type[])null), passInstance: true, "Pickable.Interact");
		}
	}
	[HarmonyPatch(typeof(Pickable), "SetPicked")]
	internal static class Pickable_SetPicked_Patch
	{
		private static void Postfix(Pickable __instance, bool picked)
		{
			if (!picked)
			{
				__instance.m_pickedLocal = false;
			}
		}
	}
	[HarmonyPatch(typeof(Attack), "Start")]
	internal static class Attack_Start_HarvestMask_Patch
	{
		private static int _baseMask;

		private static int _viewMask;

		private static void Postfix()
		{
			if (_viewMask == 0)
			{
				_viewMask = LayerMask.GetMask(new string[1] { "viewblock" });
				_baseMask = LayerMask.GetMask(new string[3] { "piece", "piece_nonsolid", "item" });
			}
			Attack.m_harvestRayMask = ((Settings.ScytheAllowHashes.Count > 0) ? (_baseMask | _viewMask) : _baseMask);
		}
	}
	[HarmonyPatch(typeof(Attack), "DoMeleeAttack")]
	internal static class Attack_DoMeleeAttack_Patch
	{
		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			return TranspilerUtil.ReplaceLerpAfterSkillFactor(instructions, AccessTools.Method(typeof(WiderSweep), "Radius", (Type[])null, (Type[])null), "Attack.DoMeleeAttack");
		}
	}
	[HarmonyPatch(typeof(Attack), "DoMeleeAttack")]
	internal static class Attack_DoMeleeAttack_Reach_Patch
	{
		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			return TranspilerUtil.ReplaceGetComponent(instructions, typeof(Pickable), AccessTools.Method(typeof(ScytheReach), "Resolve", (Type[])null, (Type[])null), "Attack.DoMeleeAttack reach");
		}
	}
	internal static class TranspilerUtil
	{
		internal static bool IsCall(CodeInstruction code, string name)
		{
			if ((code.opcode == OpCodes.Call || code.opcode == OpCodes.Callvirt) && code.operand is MethodInfo methodInfo)
			{
				return methodInfo.Name == name;
			}
			return false;
		}

		internal static IEnumerable<CodeInstruction> ReplaceSkillFactor(IEnumerable<CodeInstruction> instructions, MethodInfo replacement, bool passInstance, string patchName)
		{
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Expected O, but got Unknown
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Expected O, but got Unknown
			List<CodeInstruction> list = new List<CodeInstruction>(instructions);
			int num = 0;
			for (int i = 0; i < list.Count; i++)
			{
				if (IsCall(list[i], "GetSkillFactor"))
				{
					if (passInstance)
					{
						CodeInstruction val = new CodeInstruction(OpCodes.Ldarg_0, (object)null);
						CodeInstructionExtensions.MoveLabelsFrom(val, list[i]);
						list.Insert(i, val);
						i++;
					}
					list[i] = new CodeInstruction(OpCodes.Call, (object)replacement);
					num++;
				}
			}
			return Finish(list, instructions, num, patchName);
		}

		internal static IEnumerable<CodeInstruction> ReplaceLerpAfterSkillFactor(IEnumerable<CodeInstruction> instructions, MethodInfo replacement, string patchName)
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Expected O, but got Unknown
			List<CodeInstruction> list = new List<CodeInstruction>(instructions);
			int num = 0;
			bool flag = false;
			for (int i = 0; i < list.Count; i++)
			{
				if (IsCall(list[i], "GetSkillFactor"))
				{
					flag = true;
				}
				else if (flag && IsCall(list[i], "Lerp") && num == 0)
				{
					list[i] = new CodeInstruction(OpCodes.Call, (object)replacement);
					num++;
				}
			}
			return Finish(list, instructions, num, patchName);
		}

		internal static IEnumerable<CodeInstruction> ReplaceGetComponent(IEnumerable<CodeInstruction> instructions, Type component, MethodInfo replacement, string patchName)
		{
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Expected O, but got Unknown
			List<CodeInstruction> list = new List<CodeInstruction>(instructions);
			int num = 0;
			for (int i = 0; i < list.Count; i++)
			{
				if (IsCall(list[i], "GetComponent") && list[i].operand is MethodInfo { IsGenericMethod: not false } methodInfo && methodInfo.GetGenericArguments()[0] == component)
				{
					list[i] = new CodeInstruction(OpCodes.Call, (object)replacement);
					num++;
				}
			}
			return Finish(list, instructions, num, patchName);
		}

		private static IEnumerable<CodeInstruction> Finish(List<CodeInstruction> patched, IEnumerable<CodeInstruction> original, int hits, string patchName)
		{
			if (hits > 0)
			{
				return patched;
			}
			SkillFarmingPlugin.LogAt((LogLevel)4, "Modzified_Skill_Farming: could not find the patch point for " + patchName + ". That effect stays as in vanilla.");
			return original;
		}
	}
}
namespace ServerSync
{
	[PublicAPI]
	internal abstract class OwnConfigEntryBase
	{
		public object? LocalBaseValue;

		public bool SynchronizedConfig = true;

		public abstract ConfigEntryBase BaseConfig { get; }
	}
	[PublicAPI]
	internal class SyncedConfigEntry<T> : OwnConfigEntryBase
	{
		public readonly ConfigEntry<T> SourceConfig;

		public override ConfigEntryBase BaseConfig => (ConfigEntryBase)(object)SourceConfig;

		public T Value
		{
			get
			{
				return SourceConfig.Value;
			}
			set
			{
				SourceConfig.Value = value;
			}
		}

		public SyncedConfigEntry(ConfigEntry<T> sourceConfig)
		{
			SourceConfig = sourceConfig;
		}

		public void AssignLocalValue(T value)
		{
			if (LocalBaseValue == null)
			{
				Value = value;
			}
			else
			{
				LocalBaseValue = value;
			}
		}
	}
	internal abstract class CustomSyncedValueBase
	{
		public object? LocalBaseValue;

		public readonly string Identifier;

		public readonly Type Type;

		private object? boxedValue;

		protected bool localIsOwner;

		public object? BoxedValue
		{
			get
			{
				return boxedValue;
			}
			set
			{
				boxedValue = value;
				this.ValueChanged?.Invoke();
			}
		}

		public event Action? ValueChanged;

		protected CustomSyncedValueBase(ConfigSync configSync, string identifier, Type type)
		{
			Identifier = identifier;
			Type = type;
			configSync.AddCustomValue(this);
			localIsOwner = configSync.IsSourceOfTruth;
			configSync.SourceOfTruthChanged += delegate(bool truth)
			{
				localIsOwner = truth;
			};
		}
	}
	[PublicAPI]
	internal sealed class CustomSyncedValue<T> : CustomSyncedValueBase
	{
		public T Value
		{
			get
			{
				return (T)base.BoxedValue;
			}
			set
			{
				base.BoxedValue = value;
			}
		}

		public CustomSyncedValue(ConfigSync configSync, string identifier, T value = default(T))
			: base(configSync, identifier, typeof(T))
		{
			Value = value;
		}

		public void AssignLocalValue(T value)
		{
			if (localIsOwner)
			{
				Value = value;
			}
			else
			{
				LocalBaseValue = value;
			}
		}
	}
	internal class ConfigurationManagerAttributes
	{
		[UsedImplicitly]
		public bool? ReadOnly = false;
	}
	[PublicAPI]
	internal class ConfigSync
	{
		[HarmonyPatch(typeof(ZRpc), "HandlePackage")]
		private static class SnatchCurrentlyHandlingRPC
		{
			public static ZRpc? currentRpc;

			[HarmonyPrefix]
			private static void Prefix(ZRpc __instance)
			{
				currentRpc = __instance;
			}
		}

		[HarmonyPatch(typeof(ZNet), "Awake")]
		internal static class RegisterRPCPatch
		{
			[HarmonyPostfix]
			private static void Postfix(ZNet __instance)
			{
				isServer = __instance.IsServer();
				foreach (ConfigSync configSync2 in configSyncs)
				{
					ZRoutedRpc.instance.Register<ZPackage>(configSync2.Name + " ConfigSync", (Action<long, ZPackage>)configSync2.RPC_FromOtherClientConfigSync);
					if (isServer)
					{
						configSync2.InitialSyncDone = true;
						Debug.Log((object)("Registered '" + configSync2.Name + " ConfigSync' RPC - waiting for incoming connections"));
					}
				}
				if (isServer)
				{
					((MonoBehaviour)__instance).StartCoroutine(WatchAdminListChanges());
				}
				static void SendAdmin(List<ZNetPeer> peers, bool isAdmin)
				{
					ZPackage package = ConfigsToPackage(null, null, new PackageEntry[1]
					{
						new PackageEntry
						{
							section = "Internal",
							key = "lockexempt",
							type = typeof(bool),
							value = isAdmin
						}
					});
					ConfigSync configSync = configSyncs.First();
					if (configSync != null)
					{
						((MonoBehaviour)ZNet.instance).StartCoroutine(configSync.sendZPackage(peers, package));
					}
				}
				static IEnumerator WatchAdminListChanges()
				{
					SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance);
					List<string> CurrentList = new List<string>(adminList.GetList());
					while (true)
					{
						yield return (object)new WaitForSeconds(30f);
						if (!adminList.GetList().SequenceEqual(CurrentList))
						{
							CurrentList = new List<string>(adminList.GetList());
							List<ZNetPeer> list = (from p in ZNet.instance.GetPeers()
								where adminList.Contains(p.m_rpc.GetSocket().GetHostName())
								select p).ToList();
							SendAdmin(ZNet.instance.GetPeers().Except(list).ToList(), isAdmin: false);
							SendAdmin(list, isAdmin: true);
						}
					}
				}
			}
		}

		[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
		private static class RegisterClientRPCPatch
		{
			[HarmonyPostfix]
			private static void Postfix(ZNet __instance, ZNetPeer peer)
			{
				if (__instance.IsServer())
				{
					return;
				}
				foreach (ConfigSync configSync in configSyncs)
				{
					peer.m_rpc.Register<ZPackage>(configSync.Name + " ConfigSync", (Action<ZRpc, ZPackage>)configSync.RPC_FromServerConfigSync);
				}
			}
		}

		private class ParsedConfigs
		{
			public readonly Dictionary<OwnConfigEntryBase, object?> configValues = new Dictionary<OwnConfigEntryBase, object>();

			public readonly Dictionary<CustomSyncedValueBase, object?> customValues = new Dictionary<CustomSyncedValueBase, object>();
		}

		[HarmonyPatch(typeof(ZNet), "Shutdown")]
		private class ResetConfigsOnShutdown
		{
			[HarmonyPostfix]
			private static void Postfix()
			{
				ProcessingServerUpdate = true;
				foreach (ConfigSync configSync in configSyncs)
				{
					configSync.resetConfigsFromServer();
					configSync.IsSourceOfTruth = true;
					configSync.InitialSyncDone = false;
				}
				ProcessingServerUpdate = false;
			}
		}

		[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
		private class SendConfigsAfterLogin
		{
			private class BufferingSocket : ISocket
			{
				public volatile bool finished;

				public volatile int versionMatchQueued = -1;

				public readonly List<ZPackage> Package = new List<ZPackage>();

				public readonly ISocket Original;

				public BufferingSocket(ISocket original)
				{
					Original = original;
				}

				public bool IsConnected()
				{
					return Original.IsConnected();
				}

				public ZPackage Recv()
				{
					return Original.Recv();
				}

				public int GetSendQueueSize()
				{
					return Original.GetSendQueueSize();
				}

				public int GetCurrentSendRate()
				{
					return Original.GetCurrentSendRate();
				}

				public bool IsHost()
				{
					return Original.IsHost();
				}

				public void Dispose()
				{
					Original.Dispose();
				}

				public bool GotNewData()
				{
					return Original.GotNewData();
				}

				public void Close()
				{
					Original.Close();
				}

				public string GetEndPointString()
				{
					return Original.GetEndPointString();
				}

				public void GetAndResetStats(out int totalSent, out int totalRecv)
				{
					Original.GetAndResetStats(ref totalSent, ref totalRecv);
				}

				public void GetConnectionQuality(out float localQuality, out float remoteQuality, out int ping, out float outByteSec, out float inByteSec)
				{
					Original.GetConnectionQuality(ref localQuality, ref remoteQuality, ref ping, ref outByteSec, ref inByteSec);
				}

				public ISocket Accept()
				{
					return Original.Accept();
				}

				public int GetHostPort()
				{
					return Original.GetHostPort();
				}

				public bool Flush()
				{
					return Original.Flush();
				}

				public string GetHostName()
				{
					return Original.GetHostName();
				}

				public void VersionMatch()
				{
					if (finished)
					{
						Original.VersionMatch();
					}
					else
					{
						versionMatchQueued = Package.Count;
					}
				}

				public void Send(ZPackage pkg)
				{
					//IL_004c: Unknown result type (might be due to invalid IL or missing references)
					//IL_0052: Expected O, but got Unknown
					int pos = pkg.GetPos();
					pkg.SetPos(0);
					int num = pkg.ReadInt();
					if ((num == StringExtensionMethods.GetStableHashCode("PeerInfo") || num == StringExtensionMethods.GetStableHashCode("RoutedRPC") || num == StringExtensionMethods.GetStableHashCode("ZDOData")) && !finished)
					{
						ZPackage val = new ZPackage(pkg.GetArray());
						val.SetPos(pos);
						Package.Add(val);
					}
					else
					{
						pkg.SetPos(pos);
						Original.Send(pkg);
					}
				}
			}

			[HarmonyPriority(800)]
			[HarmonyPrefix]
			private static void Prefix(ref Dictionary<Assembly, BufferingSocket>? __state, ZNet __instance, ZRpc rpc)
			{
				//IL_0073: Unknown result type (might be due to invalid IL or missing references)
				if (__instance.IsServer())
				{
					BufferingSocket value = new BufferingSocket(rpc.GetSocket());
					AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, value);
					object? obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc });
					ZNetPeer val = (ZNetPeer)((obj is ZNetPeer) ? obj : null);
					if (val != null && (int)ZNet.m_onlineBackend != 0)
					{
						AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket").SetValue(val, value);
					}
					if (__state == null)
					{
						__state = new Dictionary<Assembly, BufferingSocket>();
					}
					__state[Assembly.GetExecutingAssembly()] = value;
				}
			}

			[HarmonyPostfix]
			private static void Postfix(Dictionary<Assembly, BufferingSocket> __state, ZNet __instance, ZRpc rpc)
			{
				ZNetPeer peer;
				if (__instance.IsServer())
				{
					object obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc });
					peer = (ZNetPeer)((obj is ZNetPeer) ? obj : null);
					if (peer == null)
					{
						SendBufferedData();
					}
					else
					{
						((MonoBehaviour)__instance).StartCoroutine(sendAsync());
					}
				}
				void SendBufferedData()
				{
					if (rpc.GetSocket() is BufferingSocket bufferingSocket)
					{
						AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket.Original);
						object? obj2 = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc });
						ZNetPeer val = (ZNetPeer)((obj2 is ZNetPeer) ? obj2 : null);
						if (val != null)
						{
							AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket").SetValue(val, bufferingSocket.Original);
						}
					}
					BufferingSocket bufferingSocket2 = __state[Assembly.GetExecutingAssembly()];
					bufferingSocket2.finished = true;
					for (int i = 0; i < bufferingSocket2.Package.Count; i++)
					{
						if (i == bufferingSocket2.versionMatchQueued)
						{
							bufferingSocket2.Original.VersionMatch();
						}
						bufferingSocket2.Original.Send(bufferingSocket2.Package[i]);
					}
					if (bufferingSocket2.Package.Count == bufferingSocket2.versionMatchQueued)
					{
						bufferingSocket2.Original.VersionMatch();
					}
				}
				IEnumerator sendAsync()
				{
					foreach (ConfigSync configSync in configSyncs)
					{
						List<PackageEntry> list = new List<PackageEntry>();
						if (configSync.CurrentVersion != null)
						{
							list.Add(new PackageEntry
							{
								section = "Internal",
								key = "serverversion",
								type = typeof(string),
								value = configSync.CurrentVersion
							});
						}
						MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null);
						SyncedList val = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance);
						list.Add(new PackageEntry
						{
							section = "Internal",
							key = "lockexempt",
							type = typeof(bool),
							value = (((object)methodInfo == null) ? ((object)val.Contains(rpc.GetSocket().GetHostName())) : methodInfo.Invoke(ZNet.instance, new object[2]
							{
								val,
								rpc.GetSocket().GetHostName()
							}))
						});
						ZPackage package = ConfigsToPackage(configSync.allConfigs.Select((OwnConfigEntryBase c) => c.BaseConfig), configSync.allCustomValues, list, partial: false);
						yield return ((MonoBehaviour)__instance).StartCoroutine(configSync.sendZPackage(new List<ZNetPeer> { peer }, package));
					}
					SendBufferedData();
				}
			}
		}

		private class PackageEntry
		{
			public string section;

			public string key;

			public Type type;

			public object? value;
		}

		[HarmonyPatch(typeof(ConfigEntryBase), "GetSerializedValue")]
		private static class PreventSavingServerInfo
		{
			[HarmonyPrefix]
			private static bool Prefix(ConfigEntryBase __instance, ref string __result)
			{
				OwnConfigEntryBase ownConfigEntryBase = configData(__instance);
				if (ownConfigEntryBase == null || isWritableConfig(ownConfigEntryBase))
				{
					return true;
				}
				__result = TomlTypeConverter.ConvertToString(ownConfigEntryBase.LocalBaseValue, __instance.SettingType);
				return false;
			}
		}

		[HarmonyPatch(typeof(ConfigEntryBase), "SetSerializedValue")]
		private static class PreventConfigRereadChangingValues
		{
			[HarmonyPrefix]
			private static bool Prefix(ConfigEntryBase __instance, string value)
			{
				OwnConfigEntryBase ownConfigEntryBase = configData(__instance);
				if (ownConfigEntryBase == null || ownConfigEntryBase.LocalBaseValue == null)
				{
					return true;
				}
				try
				{
					ownConfigEntryBase.LocalBaseValue = TomlTypeConverter.ConvertToValue(value, __instance.SettingType);
				}
				catch (Exception ex)
				{
					Debug.LogWarning((object)$"Config value of setting \"{__instance.Definition}\" could not be parsed and will be ignored. Reason: {ex.Message}; Value: {value}");
				}
				return false;
			}
		}

		private class InvalidDeserializationTypeException : Exception
		{
			public string expected;

			public string received;

			public string field = "";
		}

		public static bool ProcessingServerUpdate;

		public readonly string Name;

		public string? DisplayName;

		public string? CurrentVersion;

		public string? MinimumRequiredVersion;

		public bool ModRequired;

		private bool? forceConfigLocking;

		private bool isSourceOfTruth = true;

		private static readonly HashSet<ConfigSync> configSyncs;

		private readonly HashSet<OwnConfigEntryBase> allConfigs = new HashSet<OwnConfigEntryBase>();

		private readonly HashSet<CustomSyncedValueBase> allCustomValues = new HashSet<CustomSyncedValueBase>();

		private static bool isServer;

		private static bool lockExempt;

		private OwnConfigEntryBase? lockedConfig;

		private const byte PARTIAL_CONFIGS = 1;

		private const byte FRAGMENTED_CONFIG = 2;

		private const byte COMPRESSED_CONFIG = 4;

		private readonly Dictionary<string, SortedDictionary<int, byte[]>> configValueCache = new Dictionary<string, SortedDictionary<int, byte[]>>();

		private readonly List<KeyValuePair<long, string>> cacheExpirations = new List<KeyValuePair<long, string>>();

		private static long packageCounter;

		public bool IsLocked
		{
			get
			{
				bool? flag = forceConfigLocking;
				bool num;
				if (!flag.HasValue)
				{
					if (lockedConfig == null)
					{
						goto IL_0051;
					}
					num = ((IConvertible)lockedConfig.BaseConfig.BoxedValue).ToInt32(CultureInfo.InvariantCulture) != 0;
				}
				else
				{
					num = flag == true;
				}
				if (num)
				{
					return !lockExempt;
				}
				goto IL_0051;
				IL_0051:
				return false;
			}
			set
			{
				forceConfigLocking = value;
			}
		}

		public bool IsAdmin
		{
			get
			{
				if (!lockExempt)
				{
					return isSourceOfTruth;
				}
				return true;
			}
		}

		public bool IsSourceOfTruth
		{
			get
			{
				return isSourceOfTruth;
			}
			private set
			{
				if (value != isSourceOfTruth)
				{
					isSourceOfTruth = value;
					this.SourceOfTruthChanged?.Invoke(value);
				}
			}
		}

		public bool InitialSyncDone { get; private set; }

		public event Action<bool>? SourceOfTruthChanged;

		private event Action? lockedConfigChanged;

		static ConfigSync()
		{
			ProcessingServerUpdate = false;
			configSyncs = new HashSet<ConfigSync>();
			lockExempt = false;
			packageCounter = 0L;
			RuntimeHelpers.RunClassConstructor(typeof(VersionCheck).TypeHandle);
		}

		public ConfigSync(string name)
		{
			Name = name;
			configSyncs.Add(this);
			new VersionCheck(this);
		}

		public SyncedConfigEntry<T> AddConfigEntry<T>(ConfigEntry<T> configEntry)
		{
			OwnConfigEntryBase ownConfigEntryBase = configData((ConfigEntryBase)(object)configEntry);
			SyncedConfigEntry<T> syncedEntry = ownConfigEntryBase as SyncedConfigEntry<T>;
			if (syncedEntry == null)
			{
				syncedEntry = new SyncedConfigEntry<T>(configEntry);
				AccessTools.DeclaredField(typeof(ConfigDescription), "<Tags>k__BackingField").SetValue(((ConfigEntryBase)configEntry).Description, new object[1]
				{
					new ConfigurationManagerAttributes()
				}.Concat(((ConfigEntryBase)configEntry).Description.Tags ?? Array.Empty<object>()).Concat(new SyncedConfigEntry<T>[1] { syncedEntry }).ToArray());
				configEntry.SettingChanged += delegate
				{
					if (!ProcessingServerUpdate && syncedEntry.SynchronizedConfig)
					{
						Broadcast(0L, (ConfigEntryBase)configEntry);
					}
				};
				allConfigs.Add(syncedEntry);
			}
			return syncedEntry;
		}

		public SyncedConfigEntry<T> AddLockingConfigEntry<T>(ConfigEntry<T> lockingConfig) where T : IConvertible
		{
			if (lockedConfig != null)
			{
				throw new Exception("Cannot initialize locking ConfigEntry twice");
			}
			lockedConfig = AddConfigEntry<T>(lockingConfig);
			lockingConfig.SettingChanged += delegate
			{
				this.lockedConfigChanged?.Invoke();
			};
			return (SyncedConfigEntry<T>)lockedConfig;
		}

		internal void AddCustomValue(CustomSyncedValueBase customValue)
		{
			if (allCustomValues.Select((CustomSyncedValueBase v) => v.Identifier).Concat(new string[1] { "serverversion" }).Contains(customValue.Identifier))
			{
				throw new Exception("Cannot have multiple settings with the same name or with a reserved name (serverversion)");
			}
			allCustomValues.Add(customValue);
			customValue.ValueChanged += delegate
			{
				if (!ProcessingServerUpdate)
				{
					Broadcast(0L, customValue);
				}
			};
		}

		private void RPC_FromServerConfigSync(ZRpc rpc, ZPackage package)
		{
			lockedConfigChanged += serverLockedSettingChanged;
			IsSourceOfTruth = false;
			if (HandleConfigSyncRPC(0L, package, clientUpdate: false))
			{
				InitialSyncDone = true;
			}
		}

		private void RPC_FromOtherClientConfigSync(long sender, ZPackage package)
		{
			HandleConfigSyncRPC(sender, package, clientUpdate: true);
		}

		private bool HandleConfigSyncRPC(long sender, ZPackage package, bool clientUpdate)
		{
			//IL_0206: Unknown result type (might be due to invalid IL or missing references)
			//IL_020d: Expected O, but got Unknown
			//IL_01b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bc: Expected O, but got Unknown
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Expected O, but got Unknown
			try
			{
				if (isServer && IsLocked)
				{
					ZRpc? currentRpc = SnatchCurrentlyHandlingRPC.currentRpc;
					object obj;
					if (currentRpc == null)
					{
						obj = null;
					}
					else
					{
						ISocket socket = currentRpc.GetSocket();
						obj = ((socket != null) ? socket.GetHostName() : null);
					}
					string text = (string)obj;
					if (text != null)
					{
						MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null);
						SyncedList val = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance);
						if (!(((object)methodInfo == null) ? val.Contains(text) : ((bool)methodInfo.Invoke(ZNet.instance, new object[2] { val, text }))))
						{
							return false;
						}
					}
				}
				cacheExpirations.RemoveAll(delegate(KeyValuePair<long, string> kv)
				{
					if (kv.Key < DateTimeOffset.Now.Ticks)
					{
						configValueCache.Remove(kv.Value);
						return true;
					}
					return false;
				});
				byte b = package.ReadByte();
				if ((b & 2) != 0)
				{
					long num = package.ReadLong();
					string text2 = sender.ToString() + num;
					if (!configValueCache.TryGetValue(text2, out SortedDictionary<int, byte[]> value))
					{
						value = new SortedDictionary<int, byte[]>();
						configValueCache[text2] = value;
						cacheExpirations.Add(new KeyValuePair<long, string>(DateTimeOffset.Now.AddSeconds(60.0).Ticks, text2));
					}
					int key = package.ReadInt();
					int num2 = package.ReadInt();
					value.Add(key, package.ReadByteArray());
					if (value.Count < num2)
					{
						return false;
					}
					configValueCache.Remove(text2);
					package = new ZPackage(value.Values.SelectMany((byte[] a) => a).ToArray());
					b = package.ReadByte();
				}
				ProcessingServerUpdate = true;
				if ((b & 4) != 0)
				{
					MemoryStream stream = new MemoryStream(package.ReadByteArray());
					MemoryStream memoryStream = new MemoryStream();
					using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress))
					{
						deflateStream.CopyTo(memoryStream);
					}
					package = new ZPackage(memoryStream.ToArray());
					b = package.ReadByte();
				}
				if ((b & 1) == 0)
				{
					resetConfigsFromServer();
				}
				ParsedConfigs parsedConfigs = ReadConfigsFromPackage(package);
				foreach (KeyValuePair<OwnConfigEntryBase, object> configValue in parsedConfigs.configValues)
				{
					if (!isServer && configValue.Key.LocalBaseValue == null)
					{
						configValue.Key.LocalBaseValue = configValue.Key.BaseConfig.BoxedValue;
					}
					configValue.Key.BaseConfig.BoxedValue = configValue.Value;
				}
				foreach (KeyValuePair<CustomSyncedValueBase, object> customValue in parsedConfigs.customValues)
				{
					if (!isServer)
					{
						CustomSyncedValueBase key2 = customValue.Key;
						if (key2.LocalBaseValue == null)
						{
							key2.LocalBaseValue = customValue.Key.BoxedValue;
						}
					}
					customValue.Key.BoxedValue = customValue.Value;
				}
				Debug.Log((object)string.Format("Received {0} configs and {1} custom values from {2} for mod {3}", parsedConfigs.configValues.Count, parsedConfigs.customValues.Count, (isServer || clientUpdate) ? $"client {sender}" : "the server", DisplayName ?? Name));
				if (!isServer)
				{
					serverLockedSettingChanged();
				}
				return true;
			}
			finally
			{
				ProcessingServerUpdate = false;
			}
		}

		private ParsedConfigs ReadConfigsFromPackage(ZPackage package)
		{
			ParsedConfigs parsedConfigs = new ParsedConfigs();
			Dictionary<string, OwnConfigEntryBase> dictionary = allConfigs.Where((OwnConfigEntryBase c) => c.SynchronizedConfig).ToDictionary((OwnConfigEntryBase c) => c.BaseConfig.Definition.Section + "_" + c.BaseConfig.Definition.Key, (OwnConfigEntryBase c) => c);
			Dictionary<string, CustomSyncedValueBase> dictionary2 = allCustomValues.ToDictionary((CustomSyncedValueBase c) => c.Identifier, (CustomSyncedValueBase c) => c);
			int num = package.ReadInt();
			for (int num2 = 0; num2 < num; num2++)
			{
				string text = package.ReadString();
				string text2 = package.ReadString();
				string text3 = package.ReadString();
				Type type = Type.GetType(text3);
				if (text3 == "" || type != null)
				{
					object obj;
					try
					{
						obj = ((text3 == "") ? null : ReadValueWithTypeFromZPackage(package, type));
					}
					catch (InvalidDeserializationTypeException ex)
					{
						Debug.LogWarning((object)("Got unexpected struct internal type " + ex.received + " for field " + ex.field + " struct " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + ex.expected));
						continue;
					}
					OwnConfigEntryBase value2;
					if (text == "Internal")
					{
						CustomSyncedValueBase value;
						if (text2 == "serverversion")
						{
							if (obj?.ToString() != CurrentVersion)
							{
								Debug.LogWarning((object)("Received server version is not equal: server version = " + (obj?.ToString() ?? "null") + "; local version = " + (CurrentVersion ?? "unknown")));
							}
						}
						else if (text2 == "lockexempt")
						{
							if (obj is bool flag)
							{
								lockExempt = flag;
							}
						}
						else if (dictionary2.TryGetValue(text2, out value))
						{
							if ((text3 == "" && (!value.Type.IsValueType || Nullable.GetUnderlyingType(value.Type) != null)) || GetZPackageTypeString(value.Type) == text3)
							{
								parsedConfigs.customValues[value] = obj;
								continue;
							}
							Debug.LogWarning((object)("Got unexpected type " + text3 + " for internal value " + text2 + " for mod " + (DisplayName ?? Name) + ", expecting " + value.Type.AssemblyQualifiedName));
						}
					}
					else if (dictionary.TryGetValue(text + "_" + text2, out value2))
					{
						Type type2 = configType(value2.BaseConfig);
						if ((text3 == "" && (!type2.IsValueType || Nullable.GetUnderlyingType(type2) != null)) || GetZPackageTypeString(type2) == text3)
						{
							parsedConfigs.configValues[value2] = obj;
							continue;
						}
						Debug.LogWarning((object)("Got unexpected type " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + type2.AssemblyQualifiedName));
					}
					else
					{
						Debug.LogWarning((object)("Received unknown config entry " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ". This may happen if client and server versions of the mod do not match."));
					}
					continue;
				}
				Debug.LogWarning((object)("Got invalid type " + text3 + ", abort reading of received configs"));
				return new ParsedConfigs();
			}
			return parsedConfigs;
		}

		private static bool isWritableConfig(OwnConfigEntryBase config)
		{
			ConfigSync configSync = configSyncs.FirstOrDefault((ConfigSync cs) => cs.allConfigs.Contains(config));
			if (configSync == null)
			{
				return true;
			}
			if (!configSync.IsSourceOfTruth && config.SynchronizedConfig && config.LocalBaseValue != null)
			{
				if (!configSync.IsLocked)
				{
					if (config == configSync.lockedConfig)
					{
						return lockExempt;
					}
					return true;
				}
				return false;
			}
			return true;
		}

		private void serverLockedSettingChanged()
		{
			foreach (OwnConfigEntryBase allConfig in allConfigs)
			{
				configAttribute<ConfigurationManagerAttributes>(allConfig.BaseConfig).ReadOnly = !isWritableConfig(allConfig);
			}
		}

		private void resetConfigsFromServer()
		{
			foreach (OwnConfigEntryBase item in allConfigs.Where((OwnConfigEntryBase config) => config.LocalBaseValue != null))
			{
				item.BaseConfig.BoxedValue = item.LocalBaseValue;
				item.LocalBaseValue = null;
			}
			foreach (CustomSyncedValueBase item2 in allCustomValues.Where((CustomSyncedValueBase config) => config.LocalBaseValue != null))
			{
				item2.BoxedValue = item2.LocalBaseValue;
				item2.LocalBaseValue = null;
			}
			lockedConfigChanged -= serverLockedSettingChanged;
			serverLockedSettingChanged();
		}

		private IEnumerator<bool> distributeConfigToPeers(ZNetPeer peer, ZPackage package)
		{
			ZRoutedRpc rpc = ZRoutedRpc.instance;
			if (rpc == null)
			{
				yield break;
			}
			byte[] data = package.GetArray();
			if (data != null && data.LongLength > 250000)
			{
				int fragments = (int)(1 + (data.LongLength - 1) / 250000);
				long packageIdentifier = ++packageCounter;
				int fragment = 0;
				while (fragment < fragments)
				{
					foreach (bool item in waitForQueue())
					{
						yield return item;
					}
					if (peer.m_socket.IsConnected())
					{
						ZPackage val = new ZPackage();
						val.Write((byte)2);
						val.Write(packageIdentifier);
						val.Write(fragment);
						val.Write(fragments);
						val.Write(data.Skip(250000 * fragment).Take(250000).ToArray());
						SendPackage(val);
						if (fragment != fragments - 1)
						{
							yield return true;
						}
						int num = fragment + 1;
						fragment = num;
						continue;
					}
					break;
				}
				yield break;
			}
			foreach (bool item2 in waitForQueue())
			{
				yield return item2;
			}
			SendPackage(package);
			void SendPackage(ZPackage pkg)
			{
				string text = Name + " ConfigSync";
				if (isServer)
				{
					peer.m_rpc.Invoke(text, new object[1] { pkg });
				}
				else
				{
					rpc.InvokeRoutedRPC(peer.m_server ? 0 : peer.m_uid, text, new object[1] { pkg });
				}
			}
			IEnumerable<bool> waitForQueue()
			{
				float timeout = Time.time + 30f;
				while (peer.m_socket.GetSendQueueSize() > 20000)
				{
					if (Time.time > timeout)
					{
						Debug.Log((object)$"Disconnecting {peer.m_uid} after 30 seconds config sending timeout");
						peer.m_rpc.Invoke("Error", new object[1] { (object)(ConnectionStatus)5 });
						ZNet.instance.Disconnect(peer);
						break;
					}
					yield return false;
				}
			}
		}

		private IEnumerator sendZPackage(long target, ZPackage package)
		{
			if (!Object.op_Implicit((Object)(object)ZNet.instance))
			{
				return Enumerable.Empty<object>().GetEnumerator();
			}
			List<ZNetPeer> list = (List<ZNetPeer>)AccessTools.DeclaredField(typeof(ZRoutedRpc), "m_peers").GetValue(ZRoutedRpc.instance);
			if (target != 0L)
			{
				list = list.Where((ZNetPeer p) => p.m_uid == target).ToList();
			}
			return sendZPackage(list, package);
		}

		private IEnumerator sendZPackage(List<ZNetPeer> peers, ZPackage package)
		{
			if (!Object.op_Implicit((Object)(object)ZNet.instance))
			{
				yield break;
			}
			byte[] array = package.GetArray();
			if (array != null && array.LongLength > 10000)
			{
				ZPackage val = new ZPackage();
				val.Write((byte)4);
				MemoryStream memoryStream = new MemoryStream();
				using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionLevel.Optimal))
				{
					deflateStream.Write(array, 0, array.Length);
				}
				val.Write(memoryStream.ToArray());
				package = val;
			}
			List<IEnumerator<bool>> writers = (from p in peers
				where p.IsReady()
				select distributeConfigToPeers(p, package)).ToList();
			writers.RemoveAll((IEnumerator<bool> writer) => !writer.MoveNext());
			while (writers.Count > 0)
			{
				yield return null;
				writers.RemoveAll((IEnumerator<bool> writer) => !writer.MoveNext());
			}
		}

		private void Broadcast(long target, params ConfigEntryBase[] configs)
		{
			if (!IsLocked || isServer)
			{
				ZPackage package = ConfigsToPackage(configs);
				ZNet instance = ZNet.instance;
				if (instance != null)
				{
					((MonoBehaviour)instance).StartCoroutine(sendZPackage(target, package));
				}
			}
		}

		private void Broadcast(long target, params CustomSyncedValueBase[] customValues)
		{
			if (!IsLocked || isServer)
			{
				ZPackage package = ConfigsToPackage(null, customValues);
				ZNet instance = ZNet.instance;
				if (instance != null)
				{
					((MonoBehaviour)instance).StartCoroutine(sendZPackage(target, package));
				}
			}
		}

		private static OwnConfigEntryBase? configData(ConfigEntryBase config)
		{
			return config.Description.Tags?.OfType<OwnConfigEntryBase>().SingleOrDefault();
		}

		public static SyncedConfigEntry<T>? ConfigData<T>(ConfigEntry<T> config)
		{
			return ((ConfigEntryBase)config).Description.Tags?.OfType<SyncedConfigEntry<T>>().SingleOrDefault();
		}

		private static T configAttribute<T>(ConfigEntryBase config)
		{
			return config.Description.Tags.OfType<T>().First();
		}

		private static Type configType(ConfigEntryBase config)
		{
			return configType(config.SettingType);
		}

		private static Type configType(Type type)
		{
			if (!type.IsEnum)
			{
				return type;
			}
			return Enum.GetUnderlyingType(type);
		}

		private static ZPackage ConfigsToPackage(IEnumerable<ConfigEntryBase>? configs = null, IEnumerable<CustomSyncedValueBase>? customValues = null, IEnumerable<PackageEntry>? packageEntries = null, bool partial = true)
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Expected O, but got Unknown
			List<ConfigEntryBase> list = configs?.Where((ConfigEntryBase config) => configData(config).SynchronizedConfig).ToList() ?? new List<ConfigEntryBase>();
			List<CustomSyncedValueBase> list2 = customValues?.ToList() ?? new List<CustomSyncedValueBase>();
			ZPackage val = new ZPackage();
			val.Write(partial ? ((byte)1) : ((byte)0));
			val.Write(list.Count + list2.Count + (packageEntries?.Count() ?? 0));
			foreach (PackageEntry item in packageEntries ?? Array.Empty<PackageEntry>())
			{
				AddEntryToPackage(val, item);
			}
			foreach (CustomSyncedValueBase item2 in list2)
			{
				AddEntryToPackage(val, new PackageEntry
				{
					section = "Internal",
					key = item2.Identifier,
					type = item2.Type,
					value = item2.BoxedValue
				});
			}
			foreach (ConfigEntryBase item3 in list)
			{
				AddEntryToPackage(val, new PackageEntry
				{
					section = item3.Definition.Section,
					key = item3.Definition.Key,
					type = configType(item3),
					value = item3.BoxedValue
				});
			}
			return val;
		}

		private static void AddEntryToPackage(ZPackage package, PackageEntry entry)
		{
			package.Write(entry.section);
			package.Write(entry.key);
			package.Write((entry.value == null) ? "" : GetZPackageTypeString(entry.type));
			AddValueToZPackage(package, entry.value);
		}

		private static string GetZPackageTypeString(Type type)
		{
			return type.AssemblyQualifiedName;
		}

		private static void AddValueToZPackage(ZPackage package, object? value)
		{
			Type type = value?.GetType();
			if (value is Enum)
			{
				value = ((IConvertible)value).ToType(Enum.GetUnderlyingType(value.GetType()), CultureInfo.InvariantCulture);
			}
			else
			{
				if (value is ICollection collection)
				{
					package.Write(collection.Count);
					{
						foreach (object item in collection)
						{
							AddValueToZPackage(package, item);
						}
						return;
					}
				}
				if ((object)type != null && type.IsValueType && !type.IsPrimitive)
				{
					FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					package.Write(fields.Length);
					FieldInfo[] array = fields;
					foreach (FieldInfo fieldInfo in array)
					{
						package.Write(GetZPackageTypeString(fieldInfo.FieldType));
						AddValueToZPackage(package, fieldInfo.GetValue(value));
					}
					return;
				}
			}
			ZRpc.Serialize(new object[1] { value }, ref package);
		}

		private static object ReadValueWithTypeFromZPackage(ZPackage package, Type type)
		{
			if ((object)type != null && type.IsValueType && !type.IsPrimitive && !type.IsEnum)
			{
				FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				int num = package.ReadInt();
				if (num != fields.Length)
				{
					throw new InvalidDeserializationTypeException
					{
						received = $"(field count: {num})",
						expected = $"(field count: {fields.Length})"
					};
				}
				object uninitializedObject = FormatterServices.GetUninitializedObject(type);
				FieldInfo[] array = fields;
				foreach (FieldInfo fieldInfo in array)
				{
					string text = package.ReadString();
					if (text != GetZPackageTypeString(fieldInfo.FieldType))
					{
						throw new InvalidDeserializationTypeException
						{
							received = text,
							expected = GetZPackageTypeString(fieldInfo.FieldType),
							field = fieldInfo.Name
						};
					}
					fieldInfo.SetValue(uninitializedObject, ReadValueWithTypeFromZPackage(package, fieldInfo.FieldType));
				}
				return uninitializedObject;
			}
			if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<, >))
			{
				int num2 = package.ReadInt();
				IDictionary dictionary = (IDictionary)Activator.CreateInstance(type);
				Type type2 = typeof(KeyValuePair<, >).MakeGenericType(type.GenericTypeArguments);
				FieldInfo field = type2.GetField("key", BindingFlags.Instance | BindingFlags.NonPublic);
				FieldInfo field2 = type2.GetField("value", BindingFlags.Instance | BindingFlags.NonPublic);
				for (int j = 0; j < num2; j++)
				{
					object obj = ReadValueWithTypeFromZPackage(package, type2);
					dictionary.Add(field.GetValue(obj), field2.GetValue(obj));
				}
				return dictionary;
			}
			if (type != typeof(List<string>) && type.IsGenericType)
			{
				Type type3 = typeof(ICollection<>).MakeGenericType(type.GenericTypeArguments[0]);
				if ((object)type3 != null && type3.IsAssignableFrom(type.GetGenericTypeDefinition()))
				{
					int num3 = package.ReadInt();
					object obj2 = Activator.CreateInstance(type);
					MethodInfo method = type3.GetMethod("Add");
					for (int k = 0; k < num3; k++)
					{
						method.Invoke(obj2, new object[1] { ReadValueWithTypeFromZPackage(package, type.GenericTypeArguments[0]) });
					}
					return obj2;
				}
			}
			ParameterInfo parameterInfo = (ParameterInfo)FormatterServices.GetUninitializedObject(typeof(ParameterInfo));
			AccessTools.DeclaredField(typeof(ParameterInfo), "ClassImpl").SetValue(parameterInfo, type);
			List<object> source = new List<object>();
			ZRpc.Deserialize(new ParameterInfo[2] { null, parameterInfo }, package, ref source);
			return source.First();
		}
	}
	[PublicAPI]
	[HarmonyPatch]
	internal class VersionCheck
	{
		private static readonly HashSet<VersionCheck> versionChecks;

		private static readonly Dictionary<string, string> notProcessedNames;

		public string Name;

		private string? displayName;

		private string? currentVersion;

		private string? minimumRequiredVersion;

		public bool ModRequired = true;

		private string? ReceivedCurrentVersion;

		private string? ReceivedMinimumRequiredVersion;

		private readonly List<ZRpc> ValidatedClients = new List<ZRpc>();

		private ConfigSync? ConfigSync;

		public string DisplayName
		{
			get
			{
				return displayName ?? Name;
			}
			set
			{
				displayName = value;
			}
		}

		public string CurrentVersion
		{
			get
			{
				return currentVersion ?? "0.0.0";
			}
			set
			{
				currentVersion = value;
			}
		}

		public string MinimumRequiredVersion
		{
			get
			{
				string text = minimumRequiredVersion;
				if (text == null)
				{
					if (!ModRequired)
					{
						return "0.0.0";
					}
					text = CurrentVersion;
				}
				return text;
			}
			set
			{
				minimumRequiredVersion = value;
			}
		}

		private static void PatchServerSync()
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Expected O, but got Unknown
			Patches patchInfo = PatchProcessor.GetPatchInfo((MethodBase)AccessTools.DeclaredMethod(typeof(ZNet), "Awake", (Type[])null, (Type[])null));
			if (patchInfo != null && patchInfo.Postfixes.Count((Patch p) => p.PatchMethod.DeclaringType == typeof(ConfigSync.RegisterRPCPatch)) > 0)
			{
				return;
			}
			Harmony val = new Harmony("org.bepinex.helpers.ServerSync");
			foreach (Type item in from t in typeof(ConfigSync).GetNestedTypes(BindingFlags.NonPublic).Concat(new Type[1] { typeof(VersionCheck) })
				where t.IsClass
				select t)
			{
				val.PatchAll(item);
			}
		}

		static VersionCheck()
		{
			versionChecks = new HashSet<VersionCheck>();
			notProcessedNames = new Dictionary<string, string>();
			typeof(ThreadingHelper).GetMethod("StartSyncInvoke").Invoke(ThreadingHelper.Instance, new object[1]
			{
				new Action(PatchServerSync)
			});
		}

		public VersionCheck(string name)
		{
			Name = name;
			ModRequired = true;
			versionChecks.Add(this);
		}

		public VersionCheck(ConfigSync configSync)
		{
			ConfigSync = configSync;
			Name = ConfigSync.Name;
			versionChecks.Add(this);
		}

		public void Initialize()
		{
			ReceivedCurrentVersion = null;
			ReceivedMinimumRequiredVersion = null;
			if (ConfigSync != null)
			{
				Name = ConfigSync.Name;
				DisplayName = ConfigSync.DisplayName;
				CurrentVersion = ConfigSync.CurrentVersion;
				MinimumRequiredVersion = ConfigSync.MinimumRequiredVersion;
				ModRequired = ConfigSync.ModRequired;
			}
		}

		private bool IsVersionOk()
		{
			if (ReceivedMinimumRequiredVersion == null || ReceivedCurrentVersion == null)
			{
				return !ModRequired;
			}
			bool num = new Version(CurrentVersion) >= new Version(ReceivedMinimumRequiredVersion);
			bool flag = new Version(ReceivedCurrentVersion) >= new Version(MinimumRequiredVersion);
			return num && flag;
		}

		private string ErrorClient()
		{
			if (ReceivedMinimumRequiredVersion == null)
			{
				return "Mod " + DisplayName + " must not be installed.";
			}
			if (!(new Version(CurrentVersion) >= new Version(ReceivedMinimumRequiredVersion)))
			{
				return "Mod " + DisplayName + " requires minimum " + ReceivedMinimumRequiredVersion + ". Installed is version " + CurrentVersion + ".";
			}
			return "Mod " + DisplayName + " requires maximum " + ReceivedCurrentVersion + ". Installed is version " + CurrentVersion + ".";
		}

		private string ErrorServer(ZRpc rpc)
		{
			return "Disconnect: The client (" + rpc.GetSocket().GetHostName() + ") doesn't have the correct " + DisplayName + " version " + MinimumRequiredVersion;
		}

		private string Error(ZRpc? rpc = null)
		{
			if (rpc != null)
			{
				return ErrorServer(rpc);
			}
			return ErrorClient();
		}

		private static VersionCheck[] GetFailedClient()
		{
			return versionChecks.Where((VersionCheck check) => !check.IsVersionOk()).ToArray();
		}

		private static VersionCheck[] GetFailedServer(ZRpc rpc)
		{
			return versionChecks.Where((VersionCheck check) => check.ModRequired && !check.ValidatedClients.Contains(rpc)).ToArray();
		}

		private static void Logout()
		{
			Game.instance.Logout(true, true);
			AccessTools.DeclaredField(typeof(ZNet), "m_connectionStatus").SetValue(null, (object)(ConnectionStatus)3);
		}

		private static void DisconnectClient(ZRpc rpc)
		{
			rpc.Invoke("Error", new object[1] { 3 });
		}

		private static void CheckVersion(ZRpc rpc, ZPackage pkg)
		{
			CheckVersion(rpc, pkg, null);
		}

		private static void CheckVersion(ZRpc rpc, ZPackage pkg, Action<ZRpc, ZPackage>? original)
		{
			string text = pkg.ReadString();
			string text2 = pkg.ReadString();
			string text3 = pkg.ReadString();
			bool flag = false;
			foreach (VersionCheck versionCheck in versionChecks)
			{
				if (!(text != versionCheck.Name))
				{
					Debug.Log((object)("Received " + versionCheck.DisplayName + " version " + text3 + " and minimum version " + text2 + " from the " + (ZNet.instance.IsServer() ? "client" : "server") + "."));
					versionCheck.ReceivedMinimumRequiredVersion = text2;
					versionCheck.ReceivedCurrentVersion = text3;
					if (ZNet.instance.IsServer() && versionCheck.IsVersionOk())
					{
						versionCheck.ValidatedClients.Add(rpc);
					}
					flag = true;
				}
			}
			if (flag)
			{
				return;
			}
			pkg.SetPos(0);
			if (original != null)
			{
				original(rpc, pkg);
				if (pkg.GetPos() == 0)
				{
					notProcessedNames.Add(text, text3);
				}
			}
		}

		[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
		[HarmonyPrefix]
		private static bool RPC_PeerInfo(ZRpc rpc, ZNet __instance)
		{
			VersionCheck[] array = (__instance.IsServer() ? GetFailedServer(rpc) : GetFailedClient());
			if (array.Length == 0)
			{
				return true;
			}
			VersionCheck[] array2 = array;
			for (int i = 0; i < array2.Length; i++)
			{
				Debug.LogWarning((object)array2[i].Error(rpc));
			}
			if (__instance.IsServer())
			{
				DisconnectClient(rpc);
			}
			else
			{
				Logout();
			}
			return false;
		}

		[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
		[HarmonyPrefix]
		private static void RegisterAndCheckVersion(ZNetPeer peer, ZNet __instance)
		{
			//IL_0172: Unknown result type (might be due to invalid IL or missing references)
			//IL_0179: Expected O, but got Unknown
			notProcessedNames.Clear();
			IDictionary dictionary = (IDictionary)typeof(ZRpc).GetField("m_functions", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(peer.m_rpc);
			if (dictionary.Contains(StringExtensionMethods.GetStableHashCode("ServerSync VersionCheck")))
			{
				object obj = dictionary[StringExtensionMethods.GetStableHashCode("ServerSync VersionCheck")];
				Action<ZRpc, ZPackage> action = (Action<ZRpc, ZPackage>)obj.GetType().GetField("m_action", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(obj);
				peer.m_rpc.Register<ZPackage>("ServerSync VersionCheck", (Action<ZRpc, ZPackage>)delegate(ZRpc rpc, ZPackage pkg)
				{
					CheckVersion(rpc, pkg, action);
				});
			}
			else
			{
				peer.m_rpc.Register<ZPackage>("ServerSync VersionCheck", (Action<ZRpc, ZPackage>)CheckVersion);
			}
			foreach (VersionCheck versionCheck in versionChecks)
			{
				versionCheck.Initialize();
				if (versionCheck.ModRequired || __instance.IsServer())
				{
					Debug.Log((object)("Sending " + versionCheck.DisplayName + " version " + versionCheck.CurrentVersion + " and minimum version " + versionCheck.MinimumRequiredVersion + " to the " + (__instance.IsServer() ? "client" : "server") + "."));
					ZPackage val = new ZPackage();
					val.Write(versionCheck.Name);
					val.Write(versionCheck.MinimumRequiredVersion);
					val.Write(versionCheck.CurrentVersion);
					peer.m_rpc.Invoke("ServerSync VersionCheck", new object[1] { val });
				}
			}
		}

		[HarmonyPatch(typeof(ZNet), "Disconnect")]
		[HarmonyPrefix]
		private static void RemoveDisconnected(ZNetPeer peer, ZNet __instance)
		{
			if (!__instance.IsServer())
			{
				return;
			}
			foreach (VersionCheck versionCheck in versionChecks)
			{
				versionCheck.ValidatedClients.Remove(peer.m_rpc);
			}
		}

		[HarmonyPatch(typeof(FejdStartup), "ShowConnectError")]
		[HarmonyPostfix]
		private static void ShowConnectionError(FejdStartup __instance)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Invalid comparison between Unknown and I4
			if (!__instance.m_connectionFailedPanel.activeSelf || (int)ZNet.GetConnectionStatus() != 3)
			{
				return;
			}
			VersionCheck[] failedClient = GetFailedClient();
			if (failedClient.Length != 0)
			{
				string text = string.Join("\n", failedClient.Select((VersionCheck check) => check.Error()));
				TMP_Text connectionFailedError = __instance.m_connectionFailedError;
				connectionFailedError.text = connectionFailedError.text + "\n" + text;
			}
			foreach (KeyValuePair<string, string> item in notProcessedNames.OrderBy<KeyValuePair<string, string>, string>((KeyValuePair<string, string> kv) => kv.Key))
			{
				if (!__instance.m_connectionFailedError.text.Contains(item.Key))
				{
					TMP_Text connectionFailedError2 = __instance.m_connectionFailedError;
					connectionFailedError2.text = connectionFailedError2.text + "\n" + item.Key + " (Version: " + item.Value + ")";
				}
			}
		}
	}
}