Decompiled source of OathBound v0.1.2

plugins\OathBound.dll

Decompiled 2 months ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using ConfigReload;
using HarmonyLib;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
using PatchGuard;
using ServerSync;
using SyncedConfig;
using TMPro;
using UnityEngine;
using YamlConfig;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Core.Tokens;
using YamlDotNet.Helpers;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.BufferedDeserialization;
using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators;
using YamlDotNet.Serialization.Callbacks;
using YamlDotNet.Serialization.Converters;
using YamlDotNet.Serialization.EventEmitters;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Serialization.NodeDeserializers;
using YamlDotNet.Serialization.NodeTypeResolvers;
using YamlDotNet.Serialization.ObjectFactories;
using YamlDotNet.Serialization.ObjectGraphTraversalStrategies;
using YamlDotNet.Serialization.ObjectGraphVisitors;
using YamlDotNet.Serialization.Schemas;
using YamlDotNet.Serialization.TypeInspectors;
using YamlDotNet.Serialization.TypeResolvers;
using YamlDotNet.Serialization.Utilities;
using YamlDotNet.Serialization.ValueDeserializers;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("assembly_utils")]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("OathBound")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("OathBound: boss progression gated on the whole group")]
[assembly: AssemblyFileVersion("0.1.2.0")]
[assembly: AssemblyInformationalVersion("0.1.2")]
[assembly: AssemblyProduct("OathBound")]
[assembly: AssemblyTitle("OathBound")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.2.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 OathBound
{
	public sealed class Stage
	{
		public string Name;

		public string Key;

		public string BossPrefab;

		public int Order;
	}
	public static class Chain
	{
		public static IReadOnlyList<Stage> Stages { get; private set; } = Vanilla();

		public static event Action Changed;

		public static List<Stage> Vanilla()
		{
			return new List<Stage>
			{
				new Stage
				{
					Name = "Eikthyr",
					Key = "defeated_eikthyr",
					BossPrefab = "Eikthyr",
					Order = 1
				},
				new Stage
				{
					Name = "TheElder",
					Key = "defeated_gdking",
					BossPrefab = "gd_king",
					Order = 2
				},
				new Stage
				{
					Name = "Bonemass",
					Key = "defeated_bonemass",
					BossPrefab = "Bonemass",
					Order = 3
				},
				new Stage
				{
					Name = "Moder",
					Key = "defeated_dragon",
					BossPrefab = "Dragon",
					Order = 4
				},
				new Stage
				{
					Name = "Yagluth",
					Key = "defeated_goblinking",
					BossPrefab = "GoblinKing",
					Order = 5
				},
				new Stage
				{
					Name = "TheQueen",
					Key = "defeated_queen",
					BossPrefab = "SeekerQueen",
					Order = 6
				},
				new Stage
				{
					Name = "Fader",
					Key = "defeated_fader",
					BossPrefab = "Fader",
					Order = 7
				}
			};
		}

		public static void Set(IEnumerable<Stage> stages)
		{
			Stages = stages.OrderBy((Stage s) => s.Order).ToList();
			OathBound.Log.LogInfo((object)("Progression chain: " + string.Join(" > ", Stages.Select((Stage s) => s.Name))));
			Chain.Changed?.Invoke();
		}

		public static Stage ByBoss(string prefabName)
		{
			return Stages.FirstOrDefault((Stage s) => string.Equals(s.BossPrefab, prefabName, StringComparison.OrdinalIgnoreCase));
		}

		public static Stage ByKey(string key)
		{
			return Stages.FirstOrDefault((Stage s) => string.Equals(s.Key, key, StringComparison.OrdinalIgnoreCase));
		}

		public static Stage Find(string nameOrKey)
		{
			return Stages.FirstOrDefault((Stage s) => string.Equals(s.Name, nameOrKey, StringComparison.OrdinalIgnoreCase)) ?? ByKey(nameOrKey) ?? ByBoss(nameOrKey);
		}

		public static Stage Previous(Stage stage)
		{
			int num = Stages.ToList().IndexOf(stage);
			if (num <= 0)
			{
				return null;
			}
			return Stages[num - 1];
		}
	}
	public sealed class ChainDocument : YamlDocument
	{
		private readonly List<Stage> stages = new List<Stage>();

		protected override void ParsePrimarySection(Section sect)
		{
			int target = 0;
			string target2 = null;
			string target3 = null;
			ParseScalarFromObject(ref target, sect, "order");
			ParseStringFromObject(ref target2, sect, "key");
			ParseStringFromObject(ref target3, sect, "boss");
			if (string.IsNullOrEmpty(target2))
			{
				configErrors.Add(sect.Path + ": missing 'key', the global key the game sets when the boss dies");
			}
			if (string.IsNullOrEmpty(target3))
			{
				configErrors.Add(sect.Path + ": missing 'boss', the prefab name of the boss the altar spawns");
			}
			stages.Add(new Stage
			{
				Name = sect.dictKey,
				Key = target2,
				BossPrefab = target3,
				Order = target
			});
		}

		protected override void Verify()
		{
			foreach (IGrouping<int, Stage> item in from s in stages
				group s by s.Order into g
				where g.Count() > 1
				select g)
			{
				configWarnings.Add(string.Format("stages {0} share order {1}; their relative order is undefined", string.Join(", ", item.Select((Stage s) => s.Name)), item.Key));
			}
			if (stages.Count == 0)
			{
				configErrors.Add("the chain contains no stages");
			}
		}

		public List<Stage> Process()
		{
			return stages;
		}
	}
	public static class AltarGate
	{
		public static bool IsClosed(OfferingBowl bowl, Humanoid user)
		{
			if ((Object)(object)bowl.m_bossPrefab == (Object)null)
			{
				return false;
			}
			ProgressState.StageStatus stageStatus = ProgressState.ForBoss(((Object)bowl.m_bossPrefab).name);
			if (stageStatus == null || stageStatus.Open)
			{
				return false;
			}
			if (user != null)
			{
				((Character)user).Message((MessageType)2, ProgressState.ClosedMessage(stageStatus), 0, (Sprite)null, false);
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(OfferingBowl), "UseItem")]
	public static class OfferingBowlUseItemPatch
	{
		[HarmonyPrefix]
		public static bool Prefix(OfferingBowl __instance, Humanoid user, ref bool __result)
		{
			if (!AltarGate.IsClosed(__instance, user))
			{
				return true;
			}
			__result = false;
			return false;
		}
	}
	[HarmonyPatch(typeof(OfferingBowl), "Interact")]
	public static class OfferingBowlInteractPatch
	{
		[HarmonyPrefix]
		public static bool Prefix(OfferingBowl __instance, Humanoid user, ref bool __result)
		{
			if (!__instance.m_useItemStands || !AltarGate.IsClosed(__instance, user))
			{
				return true;
			}
			__result = false;
			return false;
		}
	}
	[HarmonyPatch(typeof(OfferingBowl), "RPC_SpawnBoss")]
	public static class OfferingBowlSpawnGuardPatch
	{
		[HarmonyPrefix]
		public static bool Prefix(OfferingBowl __instance)
		{
			if (!OathBoundConfiguration.SpawnGuard.Value || !AltarGate.IsClosed(__instance, null))
			{
				return true;
			}
			OathBound.Log.LogWarning((object)("Blocked a spawn of " + ((Object)__instance.m_bossPrefab).name + ": its stage is closed. The summoning client may have stale state."));
			return false;
		}
	}
	[HarmonyPatch(typeof(Character), "OnDeath")]
	public static class KillReporter
	{
		[HarmonyPrefix]
		public static void Prefix(Character __instance)
		{
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			if (__instance.IsPlayer() || string.IsNullOrEmpty(__instance.m_defeatSetGlobalKey))
			{
				return;
			}
			ZNetView nview = __instance.m_nview;
			if ((Object)(object)nview == (Object)null || !nview.IsValid() || !nview.IsOwner() || Chain.ByKey(__instance.m_defeatSetGlobalKey) == null)
			{
				return;
			}
			ZDO zDO = nview.GetZDO();
			List<string> list = new List<string>();
			foreach (PlayerInfo player in ZNet.instance.GetPlayerList())
			{
				int s_attackers = ZDOVars.s_attackers;
				if (zDO.GetBool(s_attackers + player.m_name, false))
				{
					list.Add(player.m_name);
				}
			}
			OathBound.Log.LogInfo((object)(__instance.m_name + " died, attackers: " + ((list.Count > 0) ? string.Join(", ", list) : "none recorded") + "."));
			ZRoutedRpc.instance.InvokeRoutedRPC("OathBound_BossDefeated", new object[3]
			{
				__instance.m_defeatSetGlobalKey,
				string.Join("\n", list),
				((Component)__instance).transform.position
			});
		}
	}
	public static class ProgressState
	{
		public sealed class StageStatus
		{
			public string BossPrefab;

			public string Name;

			public string PreviousName;

			public string Waiting;

			public bool Open => string.IsNullOrEmpty(Waiting);
		}

		private static CustomSyncedValue<string> synced;

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

		public static IReadOnlyCollection<StageStatus> Stages => byBoss.Values;

		public static void Initialize(SyncedConfiguration config)
		{
			synced = new CustomSyncedValue<string>(config.Sync, "oathbound_state", "");
			synced.ValueChanged += delegate
			{
				Guard.Run("progress state changed", Parse);
			};
		}

		public static void Assign(string text)
		{
			if (synced.Value != text)
			{
				synced.AssignLocalValue(text);
			}
		}

		private static void Parse()
		{
			byBoss.Clear();
			string[] array = (synced.Value ?? "").Split(new char[1] { '\n' });
			for (int i = 0; i < array.Length; i++)
			{
				string[] array2 = array[i].Split(new char[1] { '\t' });
				if (array2.Length >= 4 && array2[0].Length != 0)
				{
					byBoss[array2[0]] = new StageStatus
					{
						BossPrefab = array2[0],
						Name = array2[1],
						PreviousName = array2[2],
						Waiting = array2[3]
					};
				}
			}
		}

		public static StageStatus ForBoss(string bossPrefab)
		{
			if (bossPrefab == null || !byBoss.TryGetValue(bossPrefab, out var value))
			{
				return null;
			}
			return value;
		}

		public static string ClosedMessage(StageStatus status)
		{
			string text = (OathBoundConfiguration.NameMissingPlayers.Value ? status.Waiting : "the group");
			return status.Name + " will not answer. Waiting for " + text + " to defeat " + status.PreviousName + ".";
		}

		public static string Summary()
		{
			if (byBoss.Count == 0)
			{
				return "OathBound: no progression state received from the server yet.";
			}
			return "OathBound status\n" + string.Join("\n", byBoss.Values.Select((StageStatus s) => "  " + s.Name + ": " + (s.Open ? "open" : ("waiting for " + s.Waiting))));
		}
	}
	public static class Commands
	{
		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static ConsoleEvent <>9__1_0;

			public static ConsoleOptionsFetcher <>9__1_1;

			internal void <Register>b__1_0(ConsoleEventArgs args)
			{
				Guard.Run("oathbound command", new <>c__DisplayClass1_0
				{
					args = args
				}.<Register>b__2);
			}

			internal List<string> <Register>b__1_1()
			{
				return new List<string> { "status", "grant", "revoke", "ignore", "unignore", "forget" };
			}
		}

		[CompilerGenerated]
		private sealed class <>c__DisplayClass1_0
		{
			public ConsoleEventArgs args;

			internal void <Register>b__2()
			{
				Run(args);
			}
		}

		private const string Usage = "oathbound status | grant <player> <stage> | revoke <player> <stage> | ignore <player> | unignore <player> | forget <player>";

		public static void Register()
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//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_004e: Expected O, but got Unknown
			object obj = <>c.<>9__1_0;
			if (obj == null)
			{
				ConsoleEvent val = delegate(ConsoleEventArgs args)
				{
					Guard.Run("oathbound command", delegate
					{
						Run(args);
					});
				};
				<>c.<>9__1_0 = val;
				obj = (object)val;
			}
			object obj2 = <>c.<>9__1_1;
			if (obj2 == null)
			{
				ConsoleOptionsFetcher val2 = () => new List<string> { "status", "grant", "revoke", "ignore", "unignore", "forget" };
				<>c.<>9__1_1 = val2;
				obj2 = (object)val2;
			}
			new ConsoleCommand("oathbound", "oathbound status | grant <player> <stage> | revoke <player> <stage> | ignore <player> | unignore <player> | forget <player>", (ConsoleEvent)obj, false, true, false, false, false, false, (ConsoleOptionsFetcher)obj2, false, false, false);
		}

		private static void Run(ConsoleEventArgs args)
		{
			if ((Object)(object)ZNet.instance == (Object)null || ZRoutedRpc.instance == null)
			{
				Print("OathBound: not connected to a world.");
				return;
			}
			string text = ((args.Args.Length > 1) ? string.Join(" ", args.Args, 1, args.Args.Length - 1) : "status");
			ZRoutedRpc.instance.InvokeRoutedRPC("OathBound_Command", new object[1] { text });
		}

		public static void Print(string text)
		{
			if ((Object)(object)Console.instance == (Object)null)
			{
				OathBound.Log.LogInfo((object)text);
				return;
			}
			string[] array = text.Split(new char[1] { '\n' });
			foreach (string text2 in array)
			{
				((Terminal)Console.instance).AddString(text2);
			}
		}
	}
	[HarmonyPatch(typeof(Terminal), "InitTerminal")]
	public static class TerminalInitPatch
	{
		private static bool registered;

		[HarmonyPostfix]
		public static void Postfix()
		{
			if (!registered)
			{
				registered = true;
				Commands.Register();
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "Awake")]
	public static class ZNetAwakePatch
	{
		[HarmonyPostfix]
		[HarmonyPriority(200)]
		public static void Postfix(ZNet __instance)
		{
			ProgressServer.RegisterRpcs();
			if (__instance.IsServer() && __instance.IsDedicated())
			{
				OathBound.Synced.Yaml.ApplyAll();
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "OnDestroy")]
	public static class ZNetDestroyPatch
	{
		[HarmonyPostfix]
		public static void Postfix()
		{
			ProgressServer.Shutdown();
		}
	}
	[HarmonyPatch(typeof(ZNet), "RPC_PlayerID")]
	public static class ZNetPlayerIdPatch
	{
		[HarmonyPostfix]
		public static void Postfix(ZNet __instance, ZRpc rpc)
		{
			ZNetPeer peer = __instance.GetPeer(rpc);
			if (peer != null)
			{
				ProgressServer.PlayerSeen(peer.m_playerID, peer.m_playerName);
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "Disconnect")]
	public static class ZNetDisconnectPatch
	{
		[HarmonyPostfix]
		public static void Postfix(ZNetPeer peer)
		{
			if (peer != null)
			{
				ProgressServer.PlayerLeft(peer.m_playerID);
			}
		}
	}
	[HarmonyPatch(typeof(Player), "OnSpawned")]
	public static class PlayerSpawnedPatch
	{
		[HarmonyPostfix]
		public static void Postfix(Player __instance)
		{
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer() && !ZNet.instance.IsDedicated() && (Object)(object)__instance == (Object)(object)Player.m_localPlayer)
			{
				ProgressServer.PlayerSeen(__instance.GetPlayerID(), __instance.GetPlayerName());
			}
		}
	}
	[BepInPlugin("com.OathBound", "OathBound", "0.1.2")]
	public class OathBound : BaseUnityPlugin
	{
		public const string PluginGuid = "com.OathBound";

		public const string PluginName = "OathBound";

		public const string PluginVersion = "0.1.2";

		public static ManualLogSource Log { get; private set; }

		public static SyncedConfiguration Synced { get; private set; }

		private void Awake()
		{
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Synced = new SyncedConfiguration((BaseUnityPlugin)(object)this, ((BaseUnityPlugin)this).Logger, "OathBound", "0.1.2");
			OathBoundConfiguration.Initialize(Synced);
			ProgressState.Initialize(Synced);
			Chain.Changed += ProgressServer.Publish;
			Harmony val = new Harmony("com.OathBound");
			val.PatchAll(Assembly.GetExecutingAssembly());
			Synced.Finish(val);
			Guard.Install(val, ((BaseUnityPlugin)this).Logger, Assembly.GetExecutingAssembly());
			Log.LogInfo((object)"OathBound 0.1.2 loaded.");
		}
	}
	public enum LateJoinerMode
	{
		Catchup,
		Earn
	}
	public static class OathBoundConfiguration
	{
		public static ConfigEntry<bool> LockConfiguration { get; private set; }

		public static ConfigEntry<float> CreditRadius { get; private set; }

		public static ConfigEntry<bool> CreditEveryoneOnline { get; private set; }

		public static ConfigEntry<int> InactiveDays { get; private set; }

		public static ConfigEntry<bool> CountOnlyOnline { get; private set; }

		public static ConfigEntry<LateJoinerMode> LateJoiners { get; private set; }

		public static ConfigEntry<bool> SpawnGuard { get; private set; }

		public static ConfigEntry<bool> NameMissingPlayers { get; private set; }

		public static YamlConfigSource ChainSource { get; private set; }

		public static void Initialize(SyncedConfiguration config)
		{
			LockConfiguration = config.BindLocking("General", "Lock Configuration", defaultValue: true, "[Server Only] The configuration is locked and may not be changed by clients once it has been synced from the server. Only valid for server config, will have no effect on clients.");
			CreditRadius = config.Bind("Credit", "Credit Radius", 200f, "Players within this many meters of a boss when it dies are credited with defeating it even if they never hit it. Players who hit the boss are always credited. 0 disables the radius rule.", true, null);
			CreditEveryoneOnline = config.Bind("Credit", "Credit Everyone Online", false, "Credit every online player when a boss dies, regardless of distance. For groups who trust each other.", true, null);
			InactiveDays = config.Bind("Group", "Inactive Days", 14, "Players who have not logged in for this many days are not counted when checking whether the whole group has defeated a boss. 0 disables the timeout.", true, null);
			CountOnlyOnline = config.Bind("Group", "Count Only Online", false, "Only players who are online right now must have defeated the previous boss. Off means every known player counts, minus ignored and inactive ones.", true, null);
			LateJoiners = config.Bind("Group", "Late Joiners", LateJoinerMode.Catchup, "Catchup: a player joining for the first time is credited with every boss the world has already defeated. Earn: they must fight every boss.", true, null);
			SpawnGuard = config.Bind("Gate", "Spawn Guard", true, "Also block the boss spawn itself when the stage is closed, in case a client with stale state or another mod uses the altar.", true, null);
			NameMissingPlayers = config.Bind("Gate", "Name Missing Players", true, "The altar message names the players who still need to defeat the previous boss. Off says 'the group' instead.", true, null);
			ChainSource = config.AddYaml(new YamlConfigSource("OathBoundChain*.yml", "oathboundchain", () => new ChainDocument(), delegate(YamlDocument doc)
			{
				Chain.Set(((ChainDocument)doc).Process());
			})
			{
				DefaultFileContent = SyncedConfiguration.EmbeddedResource(Assembly.GetExecutingAssembly(), "OathBound.OathBoundChain.yml"),
				EditButtonName = () => "Edit progression chain"
			});
			config.Config.SettingChanged += delegate(object _, SettingChangedEventArgs args)
			{
				Guard.Run("republish after config change", delegate
				{
					if (args.ChangedSetting.Definition.Section == "Group")
					{
						ProgressServer.Publish();
					}
				});
			};
		}
	}
	public static class ProgressServer
	{
		private struct OnlinePlayer
		{
			public long Id;

			public string Name;

			public Vector3 Position;
		}

		public const string RpcBossDefeated = "OathBound_BossDefeated";

		public const string RpcCommand = "OathBound_Command";

		public const string RpcReply = "OathBound_Reply";

		private static Roster roster;

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

		public static void RegisterRpcs()
		{
			ZRoutedRpc.instance.Register<string, string, Vector3>("OathBound_BossDefeated", (Action<long, string, string, Vector3>)delegate(long sender, string key, string attackers, Vector3 position)
			{
				//IL_0015: Unknown result type (might be due to invalid IL or missing references)
				//IL_0017: Unknown result type (might be due to invalid IL or missing references)
				Guard.Run("boss defeated", delegate
				{
					//IL_000d: Unknown result type (might be due to invalid IL or missing references)
					OnBossDefeated(key, attackers, position);
				});
			});
			ZRoutedRpc.instance.Register<string>("OathBound_Command", (Action<long, string>)delegate(long sender, string line)
			{
				Guard.Run("command", delegate
				{
					OnCommand(sender, line);
				});
			});
			ZRoutedRpc.instance.Register<string>("OathBound_Reply", (Action<long, string>)delegate(long sender, string text)
			{
				Guard.Run("reply", delegate
				{
					Commands.Print(text);
				});
			});
		}

		public static void Shutdown()
		{
			roster?.Dispose();
			roster = null;
		}

		private static Roster EnsureRoster()
		{
			if (roster != null)
			{
				return roster;
			}
			roster = new Roster(ZNet.instance.GetWorldName());
			if (!roster.Load())
			{
				roster.Data.InstalledKeys = (from s in Chain.Stages
					where IsWorldKeySet(s.Key)
					select s.Key).ToList();
				roster.Save();
				OathBound.Log.LogInfo((object)("Created roster " + roster.FilePath + ((roster.Data.InstalledKeys.Count > 0) ? (", already defeated: " + string.Join(", ", roster.Data.InstalledKeys)) : "")));
			}
			roster.Changed += Publish;
			roster.Watch();
			return roster;
		}

		private static bool IsWorldKeySet(string key)
		{
			if ((Object)(object)ZoneSystem.instance != (Object)null)
			{
				return ZoneSystem.instance.GetGlobalKey(key);
			}
			return false;
		}

		public static void PlayerSeen(long id, string name)
		{
			if (!IsServer || id == 0L)
			{
				return;
			}
			Roster roster = EnsureRoster();
			bool isNew;
			RosterEntry rosterEntry = roster.Touch(id, name, out isNew);
			if (isNew && OathBoundConfiguration.LateJoiners.Value == LateJoinerMode.Catchup)
			{
				foreach (Stage stage in Chain.Stages)
				{
					if (IsWorldKeySet(stage.Key) && !rosterEntry.Cleared.Contains(stage.Key))
					{
						rosterEntry.Cleared.Add(stage.Key);
					}
				}
				if (rosterEntry.Cleared.Count > 0)
				{
					OathBound.Log.LogInfo((object)(name + " joined and caught up with the group: " + string.Join(", ", rosterEntry.Cleared)));
				}
			}
			roster.Save();
			Publish();
		}

		public static void PlayerLeft(long id)
		{
			if (IsServer && id != 0L)
			{
				RosterEntry rosterEntry = EnsureRoster().Find(id);
				if (rosterEntry != null)
				{
					rosterEntry.LastSeen = Roster.Now;
					roster.Save();
				}
				Publish();
			}
		}

		private static IEnumerable<OnlinePlayer> Online()
		{
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				if (peer.m_playerID != 0L)
				{
					yield return new OnlinePlayer
					{
						Id = peer.m_playerID,
						Name = peer.m_playerName,
						Position = peer.m_refPos
					};
				}
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null && !ZNet.instance.IsDedicated())
			{
				yield return new OnlinePlayer
				{
					Id = localPlayer.GetPlayerID(),
					Name = localPlayer.GetPlayerName(),
					Position = ((Component)localPlayer).transform.position
				};
			}
		}

		private static void OnBossDefeated(string key, string attackers, Vector3 position)
		{
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			if (!IsServer)
			{
				return;
			}
			Stage stage = Chain.ByKey(key);
			if (stage == null)
			{
				OathBound.Log.LogInfo((object)("A boss with key " + key + " died; it is not in the chain."));
				return;
			}
			Roster roster = EnsureRoster();
			HashSet<string> hashSet = new HashSet<string>(from n in attackers.Split(new char[1] { '\n' })
				where n.Length > 0
				select n, StringComparer.Ordinal);
			float value = OathBoundConfiguration.CreditRadius.Value;
			bool value2 = OathBoundConfiguration.CreditEveryoneOnline.Value;
			List<string> list = new List<string>();
			foreach (OnlinePlayer item in Online())
			{
				bool num = hashSet.Contains(item.Name);
				bool flag = value > 0f && Vector3.Distance(item.Position, position) <= value;
				if (num || flag || value2)
				{
					bool isNew;
					RosterEntry rosterEntry = roster.Touch(item.Id, item.Name, out isNew);
					if (!rosterEntry.Cleared.Contains(key))
					{
						rosterEntry.Cleared.Add(key);
						list.Add(item.Name);
					}
				}
			}
			roster.Save();
			OathBound.Log.LogInfo((object)(stage.Name + " defeated. Credited: " + ((list.Count > 0) ? string.Join(", ", list) : "nobody new") + "."));
			Publish();
		}

		private static bool Counts(RosterEntry entry, HashSet<long> online)
		{
			if (entry.Ignored)
			{
				return false;
			}
			if (online.Contains(entry.Id))
			{
				return true;
			}
			if (OathBoundConfiguration.CountOnlyOnline.Value)
			{
				return false;
			}
			int value = OathBoundConfiguration.InactiveDays.Value;
			if (value > 0)
			{
				return (DateTime.UtcNow - entry.LastSeenUtc).TotalDays <= (double)value;
			}
			return true;
		}

		public static List<string> WaitingFor(Stage stage)
		{
			Stage previous = Chain.Previous(stage);
			if (previous == null)
			{
				return new List<string>();
			}
			Roster roster = EnsureRoster();
			if (roster.Data.InstalledKeys.Contains(previous.Key))
			{
				return new List<string>();
			}
			HashSet<long> online = new HashSet<long>(from p in Online()
				select p.Id);
			return (from e in roster.Data.Players
				where Counts(e, online) && !e.Cleared.Contains(previous.Key)
				select e.Name).ToList();
		}

		public static void Publish()
		{
			if (!IsServer || roster == null)
			{
				return;
			}
			StringBuilder stringBuilder = new StringBuilder();
			foreach (Stage stage2 in Chain.Stages)
			{
				Stage stage = Chain.Previous(stage2);
				stringBuilder.Append(stage2.BossPrefab).Append('\t').Append(stage2.Name)
					.Append('\t')
					.Append(stage?.Name ?? "")
					.Append('\t')
					.Append(string.Join(", ", WaitingFor(stage2)))
					.Append('\n');
			}
			ProgressState.Assign(stringBuilder.ToString());
		}

		private static bool IsAdmin(long sender)
		{
			if (sender == ZNet.GetUID())
			{
				return true;
			}
			ZNetPeer peer = ZNet.instance.GetPeer(sender);
			if (peer != null)
			{
				return ZNet.instance.IsAdmin(peer.m_socket.GetHostName());
			}
			return false;
		}

		private static void Reply(long sender, string text)
		{
			ZRoutedRpc.instance.InvokeRoutedRPC(sender, "OathBound_Reply", new object[1] { text });
		}

		private static void OnCommand(long sender, string line)
		{
			if (!IsServer)
			{
				return;
			}
			string[] array = line.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
			string text = ((array.Length != 0) ? array[0].ToLowerInvariant() : "status");
			if (text == "status")
			{
				Reply(sender, Status());
				return;
			}
			if (!IsAdmin(sender))
			{
				Reply(sender, "OathBound: only admins can change the roster.");
				return;
			}
			Roster roster = EnsureRoster();
			switch (text)
			{
			case "grant":
			case "revoke":
			{
				if (array.Length < 3)
				{
					Reply(sender, "Usage: oathbound " + text + " <player> <stage>");
					break;
				}
				RosterEntry rosterEntry2 = roster.Find(array[1]);
				Stage stage = Chain.Find(array[2]);
				if (rosterEntry2 == null || stage == null)
				{
					Reply(sender, (rosterEntry2 == null) ? ("No player '" + array[1] + "' in the roster.") : ("No stage '" + array[2] + "' in the chain."));
					break;
				}
				if (text == "grant" && !rosterEntry2.Cleared.Contains(stage.Key))
				{
					rosterEntry2.Cleared.Add(stage.Key);
				}
				if (text == "revoke")
				{
					rosterEntry2.Cleared.Remove(stage.Key);
				}
				roster.Save();
				Publish();
				Reply(sender, rosterEntry2.Name + ": " + stage.Name + " " + ((text == "grant") ? "granted" : "revoked") + ".");
				break;
			}
			case "ignore":
			case "unignore":
			case "forget":
			{
				if (array.Length < 2)
				{
					Reply(sender, "Usage: oathbound " + text + " <player>");
					break;
				}
				RosterEntry rosterEntry = roster.Find(array[1]);
				if (rosterEntry == null)
				{
					Reply(sender, "No player '" + array[1] + "' in the roster.");
					break;
				}
				if (text == "forget")
				{
					roster.Data.Players.Remove(rosterEntry);
				}
				else
				{
					rosterEntry.Ignored = text == "ignore";
				}
				roster.Save();
				Publish();
				Reply(sender, (text == "forget") ? (rosterEntry.Name + " removed from the roster.") : (rosterEntry.Name + " is " + (rosterEntry.Ignored ? "now ignored" : "counted again") + "."));
				break;
			}
			default:
				Reply(sender, "Usage: oathbound status | grant <player> <stage> | revoke <player> <stage> | ignore <player> | unignore <player> | forget <player>");
				break;
			}
		}

		private static string Status()
		{
			Roster roster = EnsureRoster();
			HashSet<long> hashSet = new HashSet<long>(from p in Online()
				select p.Id);
			StringBuilder stringBuilder = new StringBuilder("OathBound status\n");
			foreach (Stage stage in Chain.Stages)
			{
				List<string> list = WaitingFor(stage);
				stringBuilder.Append("  ").Append(stage.Name).Append(": ")
					.Append((list.Count == 0) ? "open" : ("waiting for " + string.Join(", ", list)))
					.Append('\n');
			}
			stringBuilder.Append("Players\n");
			foreach (RosterEntry entry in roster.Data.Players)
			{
				string value = (entry.Ignored ? "ignored" : (hashSet.Contains(entry.Id) ? "online" : (Counts(entry, hashSet) ? "counted" : "inactive")));
				string text = string.Join(", ", from s in Chain.Stages
					where entry.Cleared.Contains(s.Key)
					select s.Name);
				stringBuilder.Append("  ").Append(entry.Name).Append(" (")
					.Append(entry.Id)
					.Append(") ")
					.Append(value)
					.Append(", last seen ")
					.Append(entry.LastSeen)
					.Append(", cleared: ")
					.Append((text.Length > 0) ? text : "none")
					.Append('\n');
			}
			return stringBuilder.ToString().TrimEnd(new char[1] { '\n' });
		}
	}
	public sealed class RosterEntry
	{
		public long Id { get; set; }

		public string Name { get; set; } = "";

		public string LastSeen { get; set; } = "";

		public bool Ignored { get; set; }

		public List<string> Cleared { get; set; } = new List<string>();

		public DateTime LastSeenUtc
		{
			get
			{
				if (!DateTime.TryParseExact(LastSeen, "yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out var result))
				{
					return DateTime.MinValue;
				}
				return result;
			}
		}
	}
	public sealed class RosterFile
	{
		public List<string> InstalledKeys { get; set; } = new List<string>();

		public List<RosterEntry> Players { get; set; } = new List<RosterEntry>();
	}
	public sealed class Roster
	{
		public const string TimeFormat = "yyyy-MM-dd HH:mm";

		private string lastWrittenText = "";

		private Timer poller;

		private DateTime lastWriteTime;

		private long lastLength = -1L;

		private int reloading;

		private static readonly ISerializer serializer = new SerializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).Build();

		private static readonly IDeserializer deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).IgnoreUnmatchedProperties().Build();

		public string FilePath { get; }

		public RosterFile Data { get; private set; } = new RosterFile();

		public static string Now => DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture);

		public event Action Changed;

		public Roster(string worldName)
		{
			string text = string.Join("_", worldName.Split(Path.GetInvalidFileNameChars()));
			FilePath = Path.Combine(Paths.ConfigPath, "OathBound." + text + ".roster.yml");
		}

		public bool Load()
		{
			if (!File.Exists(FilePath))
			{
				return false;
			}
			string text = File.ReadAllText(FilePath);
			Data = Parse(text) ?? new RosterFile();
			lastWrittenText = text;
			return true;
		}

		public void Save()
		{
			File.WriteAllText(contents: lastWrittenText = Header() + serializer.Serialize(Data), path: FilePath);
		}

		private static string Header()
		{
			return "# OathBound roster. Written by the server, hot reloaded when edited.\n#   ignored: true   the player never holds the group back\n#   cleared         global keys of the bosses the player has been credited with\n#   installedKeys   bosses the world had already defeated when OathBound was installed\n# Console: oathbound status | grant | revoke | ignore | unignore | forget\n";
		}

		private static RosterFile Parse(string text)
		{
			try
			{
				return deserializer.Deserialize<RosterFile>(text);
			}
			catch (Exception ex)
			{
				OathBound.Log.LogError((object)("Roster file could not be read, keeping the previous roster: " + ex.Message));
				return null;
			}
		}

		public void Watch()
		{
			if (poller == null)
			{
				Snapshot(out lastWriteTime, out lastLength);
				poller = new Timer(delegate
				{
					Poll();
				}, null, 5000, 5000);
			}
		}

		public void Dispose()
		{
			poller?.Dispose();
			poller = null;
		}

		private void Poll()
		{
			try
			{
				Snapshot(out var writeTime, out var length);
				if (writeTime == lastWriteTime && length == lastLength)
				{
					return;
				}
				lastWriteTime = writeTime;
				lastLength = length;
				if (!File.Exists(FilePath))
				{
					return;
				}
				string text = File.ReadAllText(FilePath);
				if (!(text == lastWrittenText) && Interlocked.CompareExchange(ref reloading, 1, 0) == 0)
				{
					ThreadingHelper.SynchronizingObject.BeginInvoke((Action)delegate
					{
						Reload(text);
					}, null);
				}
			}
			catch (IOException)
			{
			}
			catch (Exception ex2)
			{
				OathBound.Log.LogError((object)("Watching the roster file failed: " + ex2.Message));
			}
		}

		private void Reload(string text)
		{
			try
			{
				RosterFile rosterFile = Parse(text);
				if (rosterFile != null)
				{
					Data = rosterFile;
					lastWrittenText = text;
					OathBound.Log.LogInfo((object)"Roster file was edited, reloaded it.");
					this.Changed?.Invoke();
				}
			}
			finally
			{
				Interlocked.Exchange(ref reloading, 0);
			}
		}

		private void Snapshot(out DateTime writeTime, out long length)
		{
			FileInfo fileInfo = new FileInfo(FilePath);
			writeTime = (fileInfo.Exists ? fileInfo.LastWriteTimeUtc : DateTime.MinValue);
			length = (fileInfo.Exists ? fileInfo.Length : (-1));
		}

		public RosterEntry Find(long id)
		{
			return Data.Players.FirstOrDefault((RosterEntry p) => p.Id == id);
		}

		public RosterEntry Find(string nameOrId)
		{
			if (long.TryParse(nameOrId, out var result))
			{
				return Find(result);
			}
			return Data.Players.FirstOrDefault((RosterEntry p) => string.Equals(p.Name, nameOrId, StringComparison.OrdinalIgnoreCase));
		}

		public RosterEntry Touch(long id, string name, out bool isNew)
		{
			RosterEntry rosterEntry = Find(id);
			isNew = rosterEntry == null;
			if (isNew)
			{
				rosterEntry = new RosterEntry
				{
					Id = id
				};
				Data.Players.Add(rosterEntry);
			}
			if (!string.IsNullOrEmpty(name))
			{
				rosterEntry.Name = name;
			}
			rosterEntry.LastSeen = Now;
			return rosterEntry;
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}
namespace SyncedConfig
{
	public sealed class SyncedConfiguration
	{
		public ConfigSync Sync { get; }

		public ConfigFile Config { get; }

		public ManualLogSource Log { get; }

		public YamlConfigManager Yaml { get; }

		public YamlConfigEditor YamlEditor { get; }

		public IReadOnlyList<string> SearchPaths { get; }

		public IDisposable? ConfigWatcher { get; private set; }

		public bool IsLocked => Sync.IsLocked;

		public bool IsAdmin => Sync.IsAdmin;

		public bool IsSourceOfTruth => Sync.IsSourceOfTruth;

		public SyncedConfiguration(BaseUnityPlugin plugin, ManualLogSource log, string displayName, string version, string? minimumVersion = null, bool modRequired = true)
		{
			Config = plugin.Config;
			Log = log;
			string gUID = plugin.Info.Metadata.GUID;
			Sync = new ConfigSync(gUID)
			{
				DisplayName = displayName,
				CurrentVersion = version,
				MinimumRequiredVersion = (minimumVersion ?? version),
				ModRequired = modRequired
			};
			SearchPaths = new string[2]
			{
				Path.GetDirectoryName(Config.ConfigFilePath) ?? Paths.ConfigPath,
				Path.GetDirectoryName(((object)plugin).GetType().Assembly.Location) ?? Paths.PluginPath
			};
			Yaml = new YamlConfigManager(displayName, Log, SearchPaths, Sync);
			YamlEditor = new YamlConfigEditor(Yaml, displayName + " YAML Editor");
		}

		public ConfigEntry<T> Bind<T>(string section, string key, T defaultValue, string description, bool synced = true, AcceptableValueBase? acceptableValues = null, params object[] tags)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			return Bind(section, key, defaultValue, new ConfigDescription(description, acceptableValues, tags), synced);
		}

		public ConfigEntry<T> Bind<T>(string section, string key, T defaultValue, ConfigDescription description, bool synced = true)
		{
			ConfigEntry<T> val = Config.Bind<T>(section, key, defaultValue, description);
			Sync.AddConfigEntry<T>(val).SynchronizedConfig = synced;
			return val;
		}

		public SyncedConfigEntry<T> Add<T>(ConfigEntry<T> entry, bool synced = true)
		{
			SyncedConfigEntry<T> syncedConfigEntry = Sync.AddConfigEntry<T>(entry);
			syncedConfigEntry.SynchronizedConfig = synced;
			return syncedConfigEntry;
		}

		public ConfigEntry<T> BindLocking<T>(string section, string key, T defaultValue, string description) where T : IConvertible
		{
			ConfigEntry<T> val = Config.Bind<T>(section, key, defaultValue, description);
			Sync.AddLockingConfigEntry<T>(val);
			return val;
		}

		public YamlConfigSource AddYaml(YamlConfigSource source)
		{
			return Yaml.Register(source);
		}

		public void Finish(Harmony harmony, int yamlApplyPriority = 400)
		{
			ConfigWatcher = ConfigReloader.Setup(Config, Log);
			if (Yaml.Sources.Count > 0)
			{
				Yaml.PatchValheimHooks(harmony, yamlApplyPriority);
			}
		}

		public static Func<byte[]?> EmbeddedResource(Assembly assembly, string resourceName)
		{
			return delegate
			{
				using Stream stream = assembly.GetManifestResourceStream(resourceName);
				if (stream != null)
				{
					using MemoryStream memoryStream = new MemoryStream();
					stream.CopyTo(memoryStream);
					return memoryStream.ToArray();
				}
				return (byte[]?)null;
			};
		}
	}
}
namespace YamlConfig
{
	public sealed class YamlConfigEditor
	{
		private const int WindowId = -69;

		private readonly YamlConfigManager manager;

		private readonly string windowTitle;

		private YamlConfigSource? active;

		private Dictionary<string, string> input = new Dictionary<string, string>();

		private readonly Dictionary<string, bool> collapsed = new Dictionary<string, bool>();

		private readonly Dictionary<string, Vector2> scroll = new Dictionary<string, Vector2>();

		private Vector2 errorScroll;

		private bool hasErrors;

		public Func<string, string> Translate { get; set; } = (string s) => s;

		public bool IsOpen => active != null;

		public YamlConfigEditor(YamlConfigManager manager, string windowTitle)
		{
			this.manager = manager;
			this.windowTitle = windowTitle;
		}

		public void Open(YamlConfigSource source)
		{
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			active = source;
			input = source.Files.ToDictionary<KeyValuePair<string, string>, string, string>((KeyValuePair<string, string> kv) => kv.Key, (KeyValuePair<string, string> kv) => kv.Value);
			foreach (string key in input.Keys)
			{
				if (!collapsed.ContainsKey(key))
				{
					collapsed[key] = collapsed.Count > 3;
					scroll[key] = Vector2.zero;
				}
			}
		}

		public void Close()
		{
			active = null;
		}

		public void DrawOpenButtons()
		{
			GUILayout.BeginVertical(Array.Empty<GUILayoutOption>());
			foreach (YamlConfigSource source in manager.Sources)
			{
				if (source.Files.Count > 0 && GUILayout.Button(source.EditButtonName(), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }))
				{
					Open(source);
				}
			}
			GUILayout.EndVertical();
		}

		public void Update()
		{
			if (active != null)
			{
				Cursor.lockState = (CursorLockMode)0;
				Cursor.visible = true;
			}
		}

		public void OnGUI()
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			if (active != null)
			{
				Update();
				GUILayout.Window(-69, new Rect(10f, 10f, (float)(Screen.width - 20), (float)(Screen.height - 20)), new WindowFunction(Draw), windowTitle, Array.Empty<GUILayoutOption>());
			}
		}

		private void Draw(int id)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_04cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_04e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_04e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0505: Unknown result type (might be due to invalid IL or missing references)
			//IL_050c: Expected O, but got Unknown
			//IL_0522: Unknown result type (might be due to invalid IL or missing references)
			//IL_019c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a8: Invalid comparison between Unknown and I4
			//IL_01bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c6: Invalid comparison between Unknown and I4
			//IL_025f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0264: Unknown result type (might be due to invalid IL or missing references)
			//IL_026b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0272: Unknown result type (might be due to invalid IL or missing references)
			//IL_0279: Unknown result type (might be due to invalid IL or missing references)
			//IL_0295: Expected O, but got Unknown
			//IL_01db: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e1: Invalid comparison between Unknown and I4
			//IL_01f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fe: Expected O, but got Unknown
			//IL_02dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f1: Unknown result type (might be due to invalid IL or missing references)
			if (active == null)
			{
				return;
			}
			GUI.Box(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), (Texture)(object)Texture2D.blackTexture);
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUI.enabled = !hasErrors && (!manager.IsLocked || manager.IsSourceOfTruth);
			if (GUILayout.Button(Translate("Save and Apply"), Array.Empty<GUILayoutOption>()) && !hasErrors)
			{
				manager.SetLocalFiles(active, input, saveToDisk: true);
				GUILayout.EndHorizontal();
				Close();
				return;
			}
			if (manager.IsSourceOfTruth && GUILayout.Button(Translate("Apply without Saving"), Array.Empty<GUILayoutOption>()) && !hasErrors)
			{
				manager.SetLocalFiles(active, input, saveToDisk: false);
				GUILayout.EndHorizontal();
				Close();
				return;
			}
			GUI.enabled = true;
			if (GUILayout.Button(Translate("Discard and Close"), Array.Empty<GUILayoutOption>()))
			{
				GUILayout.EndHorizontal();
				Close();
				return;
			}
			GUILayout.EndHorizontal();
			hasErrors = false;
			string text = "";
			string text2 = "";
			foreach (string item in input.Keys.ToList())
			{
				string text3 = "yaml textarea for " + item;
				bool flag = GUI.GetNameOfFocusedControl() == text3 && Event.current.isKey;
				if (flag)
				{
					EventType type = Event.current.type;
					bool flag2 = type - 4 <= 1;
					flag = flag2;
				}
				if (flag && ((int)Event.current.keyCode == 9 || Event.current.character == '\t'))
				{
					if ((int)Event.current.type == 4)
					{
						TextEditor val = (TextEditor)GUIUtility.GetStateObject(typeof(TextEditor), GUIUtility.keyboardControl);
						val.Insert(' ');
						val.Insert(' ');
						input[item] = val.text;
					}
					Event.current.Use();
				}
				if (input.Count > 1)
				{
					GUILayout.BeginVertical(GUI.skin.box, Array.Empty<GUILayoutOption>());
					if (GUILayout.Button(Path.GetFileName(item), new GUIStyle(GUI.skin.label)
					{
						alignment = (TextAnchor)1,
						wordWrap = true,
						stretchWidth = true,
						fontSize = 15
					}, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }))
					{
						collapsed[item] = !collapsed[item];
					}
				}
				else
				{
					collapsed[item] = false;
				}
				if (!collapsed[item])
				{
					scroll[item] = GUILayout.BeginScrollView(scroll[item], (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) });
					GUI.SetNextControlName(text3);
					input[item] = GUILayout.TextArea(input[item], (GUILayoutOption[])(object)new GUILayoutOption[2]
					{
						GUILayout.ExpandWidth(true),
						GUILayout.ExpandHeight(true)
					});
					GUILayout.EndScrollView();
				}
				if (input.Count > 1)
				{
					GUILayout.EndVertical();
				}
				try
				{
					YamlDocument yamlDocument = active.CreateDocument();
					yamlDocument.Parse(YamlDocument.ParseYaml(input[item]));
					if (yamlDocument.configErrors.Count > 0)
					{
						hasErrors = true;
						text = text + "There are errors in " + Path.GetFileName(item) + ":\n" + string.Join("\n", yamlDocument.configErrors) + "\n";
					}
					if (yamlDocument.configWarnings.Count > 0)
					{
						text2 = text2 + "Warnings for " + Path.GetFileName(item) + " (not critical, but results may be unexpected):\n" + string.Join("\n", yamlDocument.configWarnings) + "\n";
					}
				}
				catch (YamlException ex)
				{
					hasErrors = true;
					text = text + "The syntax of " + Path.GetFileName(item) + " is invalid:\n" + ex.Message + ((ex.InnerException != null) ? (": " + ex.InnerException.Message) : "") + "\n";
				}
			}
			GUILayout.BeginVertical(GUI.skin.box, Array.Empty<GUILayoutOption>());
			errorScroll = GUILayout.BeginScrollView(errorScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(100f) });
			if (text != "")
			{
				GUIStyle val2 = new GUIStyle(GUI.skin.label);
				val2.normal.textColor = new Color(0.9f, 0.2f, 0.2f);
				GUILayout.Label(text, val2, Array.Empty<GUILayoutOption>());
			}
			else
			{
				GUILayout.Label(Translate("Configuration syntax is valid."), Array.Empty<GUILayoutOption>());
			}
			if (text2 != "")
			{
				GUILayout.Label(text2, Array.Empty<GUILayoutOption>());
			}
			GUILayout.EndScrollView();
			GUILayout.EndVertical();
		}
	}
	public sealed class YamlConfigManager
	{
		private readonly string modName;

		private readonly ManualLogSource log;

		private readonly List<string> searchPaths;

		private readonly ConfigSync? configSync;

		private readonly List<YamlConfigSource> sources = new List<YamlConfigSource>();

		private readonly List<FileSystemWatcher> watchers = new List<FileSystemWatcher>();

		private bool loaded;

		private static readonly List<YamlConfigManager> hookedManagers = new List<YamlConfigManager>();

		private static bool hooksInstalled;

		private static readonly FieldInfo? firstSpawnField = AccessTools.Field(typeof(Game), "m_firstSpawn");

		public IReadOnlyList<YamlConfigSource> Sources => sources;

		public bool SkipSavingOfValueChange { get; set; }

		public Func<bool> CanApply { get; set; } = () => true;

		public bool IsSourceOfTruth => configSync?.IsSourceOfTruth ?? true;

		public bool IsLocked => configSync?.IsLocked ?? false;

		public event Action<YamlConfigSource>? Applied;

		public YamlConfigManager(string modName, ManualLogSource log, IEnumerable<string> searchPaths, ConfigSync? configSync)
		{
			this.modName = modName;
			this.log = log;
			this.searchPaths = searchPaths.ToList();
			this.configSync = configSync;
		}

		public YamlConfigSource Register(YamlConfigSource source)
		{
			if (loaded)
			{
				throw new InvalidOperationException("Register sources before LoadAll");
			}
			if (configSync != null)
			{
				source.FileData = new CustomSyncedValue<List<string>>(configSync, source.Identifier, new List<string>());
			}
			sources.Add(source);
			return source;
		}

		public static Dictionary<string, string> PairsToDictionary(List<string> pairs)
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>();
			for (int i = 0; i + 1 < pairs.Count; i += 2)
			{
				dictionary[pairs[i]] = pairs[i + 1];
			}
			return dictionary;
		}

		public static List<string> DictionaryToPairs(IReadOnlyDictionary<string, string> files)
		{
			return files.SelectMany<KeyValuePair<string, string>, string>((KeyValuePair<string, string> kv) => new string[2] { kv.Key, kv.Value }).ToList();
		}

		public void LoadAll()
		{
			if (loaded)
			{
				return;
			}
			loaded = true;
			foreach (YamlConfigSource source in sources)
			{
				try
				{
					Load(source);
				}
				catch (Exception arg)
				{
					log.LogError((object)$"Failed to initialize {source.FilePattern}: {arg}");
				}
			}
		}

		public void ApplyAll()
		{
			foreach (YamlConfigSource source in sources)
			{
				if (source.Document != null)
				{
					ApplySource(source);
				}
			}
		}

		private void ApplySource(YamlConfigSource source)
		{
			try
			{
				source.Apply(source.Document);
				this.Applied?.Invoke(source);
			}
			catch (Exception arg)
			{
				log.LogError((object)$"Applying {source.FilePattern} failed: {arg}");
			}
		}

		private List<string> FindFiles(YamlConfigSource source)
		{
			return (from p in searchPaths.Where(Directory.Exists).SelectMany((string path) => Directory.GetFiles(path, source.FilePattern))
				orderby Path.GetFileName(p) != source.DefaultFileName
				select p).ThenBy(Path.GetFileName).ToList();
		}

		public bool TryParse(YamlConfigSource source, IReadOnlyDictionary<string, string> files, out YamlDocument document, string contextDescription = "the")
		{
			document = source.CreateDocument();
			bool result = true;
			foreach (KeyValuePair<string, string> file in files)
			{
				try
				{
					document.Parse(YamlDocument.ParseYaml(file.Value));
					if (document.configWarnings.Count > 0)
					{
						log.LogWarning((object)(modName + ": warnings in " + contextDescription + " " + Path.GetFileName(file.Key) + " (" + file.Key + "):\n" + string.Join("\n", document.configWarnings)));
						document.configWarnings.Clear();
					}
					if (document.configErrors.Count > 0)
					{
						log.LogError((object)(modName + ": errors in " + contextDescription + " " + Path.GetFileName(file.Key) + " (" + file.Key + "). Please review the syntax of your file:\n" + string.Join("\n", document.configErrors)));
						document.configErrors.Clear();
						result = false;
					}
				}
				catch (YamlException ex)
				{
					log.LogError((object)(modName + ": " + file.Key + " is not valid YAML, parsing failed:\n" + ex.Message + ((ex.InnerException != null) ? (": " + ex.InnerException.Message) : "")));
					result = false;
				}
			}
			return result;
		}

		private void Load(YamlConfigSource source)
		{
			List<string> list = FindFiles(source);
			if (list.Count > 0)
			{
				Dictionary<string, string> files = list.ToDictionary((string p) => p, File.ReadAllText);
				if (!TryParse(source, files, out YamlDocument document))
				{
					log.LogWarning((object)(modName + ": parts of " + source.FilePattern + " were ignored because of the errors above."));
				}
				source.Document = document;
				source.Files = files;
				if (source.FileData != null)
				{
					source.FileData.Value = DictionaryToPairs(files);
				}
			}
			else if (!WriteDefaultFile(source) && source.IsEnabled())
			{
				log.LogWarning((object)(modName + ": " + source.FilePattern + " is enabled, but no such file was found in " + string.Join(" or ", searchPaths) + "."));
			}
			if (source.FileData != null)
			{
				source.FileData.ValueChanged += delegate
				{
					OnSyncedValueChanged(source);
				};
			}
			foreach (string item in searchPaths.Where(Directory.Exists))
			{
				FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(item, source.FilePattern);
				fileSystemWatcher.Changed += delegate(object _, FileSystemEventArgs args)
				{
					OnFileChanged(source, args);
				};
				fileSystemWatcher.Created += delegate(object _, FileSystemEventArgs args)
				{
					OnFileChanged(source, args);
				};
				fileSystemWatcher.Renamed += delegate(object _, RenamedEventArgs args)
				{
					OnFileChanged(source, args);
				};
				fileSystemWatcher.IncludeSubdirectories = true;
				fileSystemWatcher.SynchronizingObject = ThreadingHelper.SynchronizingObject;
				fileSystemWatcher.EnableRaisingEvents = true;
				watchers.Add(fileSystemWatcher);
			}
		}

		private bool WriteDefaultFile(YamlConfigSource source)
		{
			if (source.DefaultFileContent == null || searchPaths.Count == 0)
			{
				return false;
			}
			try
			{
				string path = Path.Combine(searchPaths[0], source.DefaultFileName);
				if (!File.Exists(path))
				{
					byte[] array = source.DefaultFileContent();
					if (array != null)
					{
						File.WriteAllBytes(path, array);
						log.LogInfo((object)(modName + ": first run, wrote the example " + source.DefaultFileName + " to " + searchPaths[0]));
						return true;
					}
				}
			}
			catch (Exception ex)
			{
				log.LogWarning((object)(modName + ": could not write example " + source.DefaultFileName + ": " + ex.Message));
			}
			return false;
		}

		private void OnSyncedValueChanged(YamlConfigSource source)
		{
			Dictionary<string, string> dictionary = PairsToDictionary(source.FileData.Value);
			if (!TryParse(source, dictionary, out YamlDocument document, "received"))
			{
				log.LogWarning((object)(modName + ": ignoring the changed " + source.FilePattern + " data, keeping the previous configuration. Contents follow for inspection."));
				{
					foreach (KeyValuePair<string, string> item in dictionary)
					{
						log.LogWarning((object)("Contents of " + item.Key + ":"));
						log.LogInfo((object)item.Value);
					}
					return;
				}
			}
			source.Document = document;
			source.Files = dictionary;
			if (CanApply())
			{
				ApplySource(source);
			}
			if (!SkipSavingOfValueChange && IsSourceOfTruth)
			{
				WriteBack(dictionary);
			}
		}

		private void WriteBack(Dictionary<string, string> files)
		{
			foreach (KeyValuePair<string, string> file in files)
			{
				try
				{
					string text = file.Key + ".temp";
					File.WriteAllText(text, file.Value);
					if (File.Exists(file.Key))
					{
						File.Replace(text, file.Key, null);
					}
					else
					{
						File.Move(text, file.Key);
					}
				}
				catch (IOException ex)
				{
					log.LogError((object)(modName + ": failed writing config back to " + file.Key + ": " + ex.Message));
				}
			}
		}

		private void OnFileChanged(YamlConfigSource source, FileSystemEventArgs args)
		{
			if (!Path.GetFileName(args.Name).StartsWith(Regex.Replace(source.FilePattern, "\\*.*", ""), StringComparison.OrdinalIgnoreCase))
			{
				return;
			}
			List<string> list = FindFiles(source);
			if (list.Count == 0)
			{
				return;
			}
			Dictionary<string, string> files;
			try
			{
				files = list.ToDictionary((string p) => p, File.ReadAllText);
			}
			catch (IOException)
			{
				return;
			}
			if (!TryParse(source, files, out YamlDocument _))
			{
				log.LogWarning((object)(modName + ": ignoring the changed " + source.FilePattern + ", keeping the previously loaded configuration."));
				return;
			}
			SetLocalFiles(source, files, saveToDisk: false);
			log.LogInfo((object)(modName + ": reloaded " + source.FilePattern + (IsSourceOfTruth ? "" : ", but did not apply it because the server's configuration is active.")));
		}

		public void SetLocalFiles(YamlConfigSource source, IReadOnlyDictionary<string, string> files, bool saveToDisk)
		{
			bool skipSavingOfValueChange = SkipSavingOfValueChange;
			SkipSavingOfValueChange = !saveToDisk;
			try
			{
				if (source.FileData != null)
				{
					source.FileData.AssignLocalValue(DictionaryToPairs(files));
				}
				else
				{
					if (!TryParse(source, files, out YamlDocument document))
					{
						return;
					}
					source.Document = document;
					source.Files = files;
					if (CanApply())
					{
						ApplySource(source);
					}
					if (saveToDisk)
					{
						WriteBack(files.ToDictionary<KeyValuePair<string, string>, string, string>((KeyValuePair<string, string> kv) => kv.Key, (KeyValuePair<string, string> kv) => kv.Value));
					}
				}
			}
			finally
			{
				SkipSavingOfValueChange = skipSavingOfValueChange;
			}
		}

		public void PatchValheimHooks(Harmony harmony, int applyPriority = 400)
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Expected O, but got Unknown
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Expected O, but got Unknown
			hookedManagers.Add(this);
			if (!hooksInstalled)
			{
				hooksInstalled = true;
				harmony.Patch((MethodBase)AccessTools.Method(typeof(FejdStartup), "Start", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(YamlConfigManager), "OnFejdStartup", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.Method(typeof(ZNet), "Awake", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(YamlConfigManager), "OnZNetAwake", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.Method(typeof(Game), "RequestRespawn", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(YamlConfigManager), "OnRequestRespawn", (Type[])null)
				{
					priority = applyPriority
				}, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
		}

		private static void OnFejdStartup()
		{
			foreach (YamlConfigManager hookedManager in hookedManagers)
			{
				hookedManager.LoadAll();
			}
		}

		private static void OnZNetAwake(ZNet __instance)
		{
			if (!__instance.IsDedicated() || !__instance.IsServer())
			{
				return;
			}
			foreach (YamlConfigManager hookedManager in hookedManagers)
			{
				hookedManager.LoadAll();
			}
		}

		private static void OnRequestRespawn(Game __instance)
		{
			object obj = firstSpawnField?.GetValue(__instance);
			if (obj is bool && !(bool)obj)
			{
				return;
			}
			foreach (YamlConfigManager hookedManager in hookedManagers)
			{
				hookedManager.ApplyAll();
			}
		}
	}
	public sealed class YamlConfigSource
	{
		public string FilePattern { get; }

		public string Identifier { get; }

		public Func<YamlDocument> CreateDocument { get; }

		public Action<YamlDocument> Apply { get; }

		public Func<bool> IsEnabled { get; set; } = () => true;

		public Func<byte[]?>? DefaultFileContent { get; set; }

		public Func<string> EditButtonName { get; set; }

		public YamlDocument? Document { get; internal set; }

		public CustomSyncedValue<List<string>>? FileData { get; internal set; }

		public IReadOnlyDictionary<string, string> Files { get; internal set; } = new Dictionary<string, string>();

		public string DefaultFileName => FilePattern.Replace("*", "");

		public YamlConfigSource(string filePattern, string identifier, Func<YamlDocument> createDocument, Action<YamlDocument> apply)
		{
			FilePattern = filePattern;
			Identifier = identifier;
			CreateDocument = createDocument;
			Apply = apply;
			EditButtonName = () => "Edit " + filePattern.Replace("*", "");
		}
	}
	public abstract class YamlDocument
	{
		public sealed class Section : IDisposable
		{
			private readonly Section? parent;

			private readonly Dictionary<string, object?> dict;

			public readonly string dictKey;

			private readonly HashSet<string> knownKeys = new HashSet<string>(StringComparer.CurrentCultureIgnoreCase);

			private readonly YamlDocument document;

			public string Path => ((parent == null || parent.dictKey == "") ? "" : (parent.Path + ".")) + dictKey;

			public IEnumerable<string> Keys => dict.Keys;

			public Section(Dictionary<string, object?> dict, string key, Section? parent, YamlDocument document)
			{
				this.dict = dict;
				dictKey = key;
				this.parent = parent;
				this.document = document;
			}

			public object? Get(string key)
			{
				knownKeys.Add(key);
				if (dict.TryGetValue(key, out object value) && value == null)
				{
					document.configErrors.Add(Path + "." + key + " cannot be empty");
				}
				return value;
			}

			public bool Has(string key)
			{
				return dict.ContainsKey(key);
			}

			public IEnumerable<string> KeysWithPrefix(string prefix)
			{
				return dict.Keys.Where((string k) => k.StartsWith(prefix, StringComparison.OrdinalIgnoreCase));
			}

			public void Dispose()
			{
				string[] array = dict.Keys.Where((string key) => !knownKeys.Contains(key)).ToArray();
				if (array.Length != 0)
				{
					document.configWarnings.Add(Path + " contains unknown keys " + string.Join(", ", array));
				}
			}
		}

		public delegate bool ParserDelegate<T>(ref T target, Section sect, string key);

		public readonly List<string> configErrors = new List<string>();

		public readonly List<string> configWarnings = new List<string>();

		protected readonly Dictionary<string, List<string>> groups = new Dictionary<string, List<string>>();

		private static readonly string[] falseWords = new string[12]
		{
			"0", "false", "off", "no", "nope", "nah", "-", "hell no", "pls dont", "lol no",
			"certainly not", "absolutely not"
		};

		private static readonly string[] trueWords = new string[14]
		{
			"1", "true", "on", "yes", "yep", "yeah", "+", "hell yeah", "ok", "okay",
			"k", "yaaas", "certainly", "absolutely"
		};

		public IReadOnlyDictionary<string, List<string>> Groups => groups;

		protected virtual int LevelCount => 1;

		protected virtual bool TryParseCustomScalar(Type type, string input, out object? output)
		{
			output = null;
			return false;
		}

		public static object? ParseYaml(string text)
		{
			return new DeserializerBuilder().Build().Deserialize<object>(text.TrimStart(new char[1] { '\ufeff' }));
		}

		public void Parse(object? configObj)
		{
			if (configObj is Dictionary<object, object> dict)
			{
				Dictionary<string, object> dictionary = castDictToStringDict(dict);
				foreach (KeyValuePair<string, object> item in dictionary)
				{
					if (string.Equals(item.Key, "groups", StringComparison.OrdinalIgnoreCase))
					{
						ParseGroups(item.Value);
						continue;
					}
					Section section = this.section(new Section(dictionary, "", null, this), item.Key);
					if (section != null)
					{
						ParsePrimarySection(section);
						section.Dispose();
						continue;
					}
					configErrors.Add("All top-level keys must be a mapping. Got unexpected " + (item.Value?.GetType().ToString() ?? "null") + " for key '" + item.Key + "'.");
				}
			}
			else if (configObj != null)
			{
				configErrors.Add("The file must be a mapping of names to settings, got " + configObj.GetType().Name);
			}
			Verify();
		}

		private void ParseGroups(object? value)
		{
			if (!(value is Dictionary<object, object> dictionary))
			{
				configErrors.Add("groups must contain a mapping from group to a list of group or member names");
				return;
			}
			foreach (KeyValuePair<object, object> item2 in dictionary)
			{
				string text = item2.Key.ToString();
				List<string> source;
				if (item2.Value is string item)
				{
					source = new List<string> { item };
				}
				else
				{
					if (!(item2.Value is List<object> source2))
					{
						configErrors.Add("groups." + text + " contains an invalid value (" + (item2.Value?.GetType().Name ?? "null") + ")");
						continue;
					}
					source = (from s in source2
						where s != null
						select s.ToString()).ToList();
				}
				groups[text.ToLower()] = source.Select((string s) => s.ToLower()).ToList();
			}
		}

		protected abstract void ParsePrimarySection(Section primarySection);

		protected virtual void Verify()
		{
		}

		protected void resolveGroupsForName(List<string> groupNames, string name)
		{
			foreach (KeyValuePair<string, List<string>> group in groups)
			{
				if (!groupNames.Contains(group.Key) && group.Value.Contains(name))
				{
					groupNames.Add(group.Key);
					resolveGroupsForName(groupNames, group.Key);
				}
			}
		}

		public List<string> ResolveNames(IEnumerable<string> ownNames, params string[] trailingSpecialGroups)
		{
			List<string> list = ownNames.Select((string n) => n.ToLower()).Distinct().ToList();
			string[] array = list.ToArray();
			foreach (string name in array)
			{
				resolveGroupsForName(list, name);
			}
			list.AddRange(trailingSpecialGroups.Select((string g) => g.ToLower()));
			return list;
		}

		protected static void AssignConfig<T>(ref T? newConfig, T? inConfig) where T : class
		{
			if (newConfig == null)
			{
				newConfig = inConfig;
			}
		}

		protected static void AssignConfig<T>(ref T? newConfig, T? inConfig) where T : struct
		{
			T? val = newConfig;
			if (!val.HasValue)
			{
				newConfig = inConfig;
			}
		}

		protected static void AssignEnumConfig<T>(ref Dictionary<int, Dictionary<T, float[]?>>? newConfig, Dictionary<int, Dictionary<T, float[]?>>? inConfig) where T : notnull
		{
			if (inConfig == null)
			{
				return;
			}
			if (newConfig == null)
			{
				newConfig = inConfig.ToDictionary<KeyValuePair<int, Dictionary<T, float[]>>, int, Dictionary<T, float[]>>((KeyValuePair<int, Dictionary<T, float[]>> kv) => kv.Key, (KeyValuePair<int, Dictionary<T, float[]>> kv) => kv.Value.ToDictionary((KeyValuePair<T, float[]> k) => k.Key, (KeyValuePair<T, float[]> k) => k.Value));
			}
			foreach (KeyValuePair<int, Dictionary<T, float[]>> item in inConfig)
			{
				if (!newConfig.ContainsKey(item.Key))
				{
					newConfig[item.Key] = item.Value;
					continue;
				}
				foreach (KeyValuePair<T, float[]> item2 in item.Value)
				{
					if (!newConfig[item.Key].ContainsKey(item2.Key))
					{
						newConfig[item.Key][item2.Key] = item2.Value;
					}
				}
			}
		}

		protected void fillLevels<T>(Dictionary<int, T>? config)
		{
			if (config == null)
			{
				return;
			}
			T value = default(T);
			int num = -1;
			for (int i = 0; i < LevelCount; i++)
			{
				if (config.TryGetValue(i, out T value2))
				{
					value = value2;
					if (num == -1)
					{
						num = i;
					}
				}
				else if (num != -1)
				{
					config[i] = value;
				}
			}
			for (int j = 0; j < num; j++)
			{
				config[j] = config[num];
			}
		}

		protected static Dictionary<string, object?> castDictToStringDict(Dictionary<object, object?> dict)
		{
			Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.CurrentCultureIgnoreCase);
			foreach (KeyValuePair<object, object> item in dict)
			{
				dictionary[item.Key.ToString()] = item.Value;
			}
			return dictionary;
		}

		protected Section? section(Section parent, string key)
		{
			object obj = parent.Get(key);
			if (obj is Dictionary<object, object> dict)
			{
				return new Section(castDictToStringDict(dict), key, parent, this);
			}
			if (obj != null)
			{
				configWarnings.Add($"Expected a section containing further configuration values, got {obj.GetType()} for {parent.Path}.{key}");
			}
			return null;
		}

		private bool parseScalar(Type type, string input, out object? output)
		{
			if (type == typeof(int))
			{
				if (int.TryParse(input, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
				{
					output = result;
					return true;
				}
			}
			else if (type == typeof(float))
			{
				if (float.TryParse(input, NumberStyles.Float, CultureInfo.InvariantCulture, out var result2))
				{
					output = result2;
					return true;
				}
			}
			else if (type == typeof(bool))
			{
				if (falseWords.Contains(input.ToLower()))
				{
					output = false;
					return true;
				}
				if (trueWords.Contains(input.ToLower()))
				{
					output = true;
					return true;
				}
			}
			else if (type.IsEnum)
			{
				try
				{
					output = Enum.Parse(type, input, ignoreCase: true);
					return true;
				}
				catch (ArgumentException)
				{
				}
			}
			else if (TryParseCustomScalar(type, input, out output))
			{
				return true;
			}
			output = null;
			return false;
		}

		protected static bool TryParseBoolWord(string input, out bool value)
		{
			if (falseWords.Contains(input.ToLower()))
			{
				value = false;
				return true;
			}
			if (trueWords.Contains(input.ToLower()))
			{
				value = true;
				return true;
			}
			value = false;
			return false;
		}

		private bool parseScalar<T>(string input, out T output) where T : struct
		{
			if (parseScalar(typeof(T), input, out object output2) && output2 is T val)
			{
				output = val;
				return true;
			}
			output = default(T);
			return false;
		}

		protected bool ParseScalarFromObject<T>(ref T target, Section sect, string key) where T : struct
		{
			T? target2 = null;
			if (ParseScalarFromObject(ref target2, sect, key))
			{
				target = target2.Value;
				return true;
			}
			return false;
		}

		protected bool ParseScalarFromObject<T>(ref T? target, Section sect, string key) where T : struct
		{
			object obj = sect.Get(key);
			if (obj == null)
			{
				return false;
			}
			if (obj is string input && parseScalar<T>(input, out var output))
			{
				target = output;
				return true;
			}
			configErrors.Add(sect.Path + "." + key + " is not a " + typeof(T).Name + " (" + key + ")");
			return false;
		}

		protected bool ParseStringFromObject(ref string? target, Section sect, string key)
		{
			object obj = sect.Get(key);
			if (obj is string text)
			{
				target = text;
				return true;
			}
			if (obj != null)
			{
				configErrors.Add(sect.Path + "." + key + " is not a text value");
			}
			return false;
		}

		private bool parseStructFromDict<T>(Dictionary<object, object?> input, out T output, string path) where T : struct
		{
			output = default(T);
			if (typeof(T).IsPrimitive || typeof(T).IsEnum)
			{
				return false;
			}
			object obj = output;
			HashSet<string> hashSet = new HashSet<string>();
			Dictionary<string, object> dictionary = castDictToStringDict(input);
			FieldInfo[] fields = typeof(T).GetFields(BindingFlags.Instance | BindingFlags.Public);
			foreach (FieldInfo fieldInfo in fields)
			{
				if (dictionary.TryGetValue(fieldInfo.Name, out var value))
				{
					hashSet.Add(fieldInfo.Name);
					if (value is string input2 && parseScalar(fieldInfo.FieldType, input2, out object output2))
					{
						fieldInfo.SetValue(obj, output2);
						continue;
					}
					configErrors.Add(path + "." + fieldInfo.Name + " is not " + fieldInfo.FieldType.Name);
				}
			}
			foreach (string key in dictionary.Keys)
			{
				if (!hashSet.Contains(key))
				{
					configErrors.Add(path + " contains invalid key '" + key + "'. Allowed keys are " + string.Join(", ", from f in typeof(T).GetFields()
						select f.Name) + ".");
				}
			}
			output = (T)obj;
			return true;
		}

		protected float[]? ParseFloatList(string path, object? value)
		{
			if (!(value is List<object> list))
			{
				return null;
			}
			float[] array = new float[list.Count];
			for (int i = 0; i < list.Count; i++)
			{
				if (list[i] is string s && float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
				{
					array[i] = result;
				}
				else
				{
					configErrors.Add($"{path}[{i}] is not a number");
				}
			}
			return array;
		}

		protected List<string>? ParseStringList(string path, object? value)
		{
			if (value is string item)
			{
				return new List<string> { item };
			}
			if (!(value is List<object> source))
			{
				return null;
			}
			return (from s in source
				where s != null
				select s.ToString()).ToList();
		}

		private Dictionary<int, T> Repeat<T>(T value)
		{
			return Enumerable.Range(0, LevelCount).ToDictionary((int i) => i, (int _) => value);
		}

		protected bool ParseLevelledScalar<T>(ref Dictionary<int, T?>? target, Section sect, string key) where T : struct
		{
			string name = typeof(T).Name;
			object obj = sect.Get(key);
			if (obj == null)
			{
				return target != null;
			}
			T output;
			if (obj.ToString().ToLower() == "default")
			{
				target = Repeat<T?>(null);
			}
			else if (obj is string input && parseScalar<T>(input, out output))
			{
				target = Repeat((T?)output);
			}
			else if (obj is Dictionary<object, object> dictionary)
			{
				target = new Dictionary<int, T?>();
				bool flag = true;
				foreach (KeyValuePair<object, object> item in dictionary)
				{
					if (int.TryParse(item.Key.ToString(), out var result))
					{
						T output2;
						T output3;
						if (item.Value?.ToString().ToLower() == "default")
						{
							target[result] = null;
						}
						else if (item.Value is string input2 && parseScalar<T>(input2, out output2))
						{
							target[result] = output2;
						}
						else if (item.Value is Dictionary<object, object> input3 && parseStructFromDict<T>(input3, out output3, $"{sect.Path}.{key}.{result}"))
						{
							target[result] = output3;
						}
						else
						{
							configErrors.Add($"{sect.Path}.{key}.{result} is not 'default' nor {name}");
						}
					}
					else
					{
						if (flag && !typeof(T).IsPrimitive && !typeof(T).IsEnum && parseStructFromDict<T>(dictionary, out var output4, sect.Path + "." + key))
						{
							target = Repeat((T?)output4);
							break;
						}
						configErrors.Add($"{sect.Path}.{key} contains a non-integer key {item.Key}");
					}
					flag = false;
				}
			}
			else
			{
				configErrors.Add(sect.Path + "." + key + " is not 'default', " + name + " nor a map");
			}
			return target != null;
		}

		protected bool ParseLevelledFloatList(ref Dictionary<int, float[]?>? target, Section sect, string key)
		{
			return ParseLevelledFloatList(ref target, sect, key, null);
		}

		protected bool ParseLevelledFloatList(ref Dictionary<int, float[]?>? target, Section sect, string key, Func<string, float[]?>? interpretScalar = null)
		{
			object obj = sect.Get(key);
			if (obj == null)
			{
				return target != null;
			}
			string text = ((interpretScalar != null) ? "integer" : "number");
			if (obj.ToString().ToLower() == "default")
			{
				target = Repeat<float[]>(null);
			}
			else
			{
				if (obj is string s)
				{
					float[] array = fromScalar(s);
					if (array != null)
					{
						target = Repeat(array);
						goto IL_023f;
					}
				}
				float[] array2 = ParseFloatList(sect.Path + "." + key, obj);
				if (array2 != null)
				{
					target = Repeat(array2);
				}
				else if (obj is Dictionary<object, object> dictionary)
				{
					target = new Dictionary<int, float[]>();
					foreach (KeyValuePair<object, object> item in dictionary)
					{
						if (!int.TryParse(item.Key.ToString(), out var result))
						{
							configErrors.Add($"{sect.Path}.{key} contains a non-integer key {item.Key}");
							continue;
						}
						if (item.Value?.ToString().ToLower() == "default")
						{
							target[result] = null;
							continue;
						}
						if (item.Value is string s2)
						{
							float[] array3 = fromScalar(s2);
							if (array3 != null)
							{
								target[result] = array3;
								continue;
							}
						}
						float[] array4 = ParseFloatList($"{sect.Path}.{key}.{result}", item.Value);
						if (array4 != null)
						{
							target[result] = array4;
							continue;
						}
						configErrors.Add($"{sect.Path}.{key}.{result} is not 'default', {text} nor a list");
					}
				}
				else
				{
					configErrors.Add(sect.Path + "." + key + " is not 'default', " + text + ", a list nor a map");
				}
			}
			goto IL_023f;
			IL_023f:
			return target != null;
			float[]? fromScalar(string text2)
			{
				if (interpretScalar != null)
				{
					return interpretScalar(text2);
				}
				if (float.TryParse(text2, NumberStyles.Float, CultureInfo.InvariantCulture, out var result2))
				{
					return new float[1] { result2 };
				}
				return null;
			}
		}

		protected bool ParseLevelledEnumMap<T>(ref Dictionary<int, Dictionary<T, float[]?>>? target, Section sect, string key) where T : struct, IConvertible
		{
			return ParseLevelledEnumMap(ref target, sect, key, null);
		}

		protected bool ParseLevelledEnumMap<T>(ref Dictionary<int, Dictionary<T, float[]?>>? target, Section sect, string key, T? bareValueKey = null) where T : struct, IConvertible
		{
			List<T> allEnumValues = ((T[])Enum.GetValues(typeof(T))).Where((T t) => t.ToInt32(CultureInfo.InvariantCulture) != 0).ToList();
			object obj = sect.Get(key);
			if (obj == null)
			{
				return target != null;
			}
			if (obj.ToString().ToLower() == "default")
			{
				target = Repeat(allDefault());
			}
			else if (obj is string text)
			{
				T result2;
				if (float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
				{
					target = Repeat(fromNumber(result));
				}
				else if (!bareValueKey.HasValue && Enum.TryParse<T>(text, ignoreCase: true, out result2))
				{
					target = Repeat(new Dictionary<T, float[]> { 
					{
						result2,
						new float[1] { 100f }
					} });
				}
				else
				{
					configErrors.Add(sect.Path + "." + key + " contains invalid value " + text + ", expecting default, a number, " + ((!bareValueKey.HasValue) ? (string.Join(", ", allEnumValues) + " or a map") : "a list or a map"));
				}
			}
			else
			{
				if (bareValueKey.HasValue)
				{
					T valueOrDefault = bareValueKey.GetValueOrDefault();
					float[] array = ParseFloatList(sect.Path + "." + key, obj);
					if (array != null)
					{
						target = Repeat(new Dictionary<T, float[]> { { valueOrDefault, array } });
						goto IL_03fa;
					}
				}
				if (obj is Dictionary<object, object> dictionary)
				{
					target = new Dictionary<int, Dictionary<T, float[]>>();
					if (dictionary.Count > 0)
					{
						if (int.TryParse(dictionary.Keys.First().ToString(), out var _))
						{
							foreach (KeyValuePair<object, object> item in dictionary)
							{
								float result5;
								if (!int.TryParse(item.Key.ToString(), out var result4))
								{
									configErrors.Add(sect.Path + "." + key + " mixes level numbers and names as keys");
								}
								else if (item.Value?.ToString().ToLower() == "default")
								{
									target[result4] = allDefault();
								}
								else if (item.Value is string s && float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out result5))
								{
									target[result4] = fromNumber(result5);
								}
								else if (item.Value is Dictionary<object, object> dict)
								{
									target[result4] = ParseEnumChances($"{sect.Path}.{key}[{result4}]", dict, allEnumValues);
								}
								else
								{
									configErrors.Add($"{sect.Path}.{key}[{result4}] is not a mapping from names to chances");
								}
							}
						}
						else
						{
							Dictionary<T, float[]> value = ParseEnumChances(sect.Path + "." + key, dictionary, allEnumValues);
							for (int num = 0; num < LevelCount; num++)
							{
								target[num] = value;
							}
						}
					}
				}
				else
				{
					configErrors.Add(sect.Path + "." + key + " is not a number" + (bareValueKey.HasValue ? ", a list" : "") + ", 'default' nor a map");
				}
			}
			goto IL_03fa;
			IL_03fa:
			return target != null;
			Dictionary<T, float[]?> allDefault()
			{
				return allEnumValues.ToDictionary((T k) => k, (T _) => (float[])null);
			}
			Dictionary<T, float[]?> fromNumber(float probability)
			{
				if (!bareValueKey.HasValue)
				{
					return allEnumValues.ToDictionary((T k) => k, (T _) => new float[1] { probability });
				}
				T valueOrDefault2 = bareValueKey.GetValueOrDefault();
				return new Dictionary<T, float[]> { 
				{
					valueOrDefault2,
					new float[1] { probability }
				} };
			}
		}

		private Dictionary<T, float[]?> ParseEnumChances<T>(string path, Dictionary<object, object?> dict, List<T> allEnumValues) where T : struct, IConvertible
		{
			Dictionary<T, float[]> dictionary = new Dictionary<T, float[]>();
			bool flag = false;
			float[] array = null;
			foreach (KeyValuePair<object, object> item in dict)
			{
				string text = item.Key.ToString();
				bool flag2 = text.ToLower() == "other";
				T result = default(T);
				if (!flag2 && !Enum.TryParse<T>(text, ignoreCase: true, out result))
				{
					configErrors.Add(path + " contains unexpected key " + text + ", expecting either other, " + string.Join(", ", allEnumValues));
					continue;
				}
				float[] array2;
				float result2;
				if (item.Value?.ToString().ToLower() == "default")
				{
					array2 = null;
				}
				else if (item.Value is string s && float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out result2))
				{
					array2 = new float[1] { result2 };
				}
				else
				{
					float[] array3 = ParseFloatList(path + "." + text, item.Value);
					if (array3 == null)
					{
						configErrors.Add(string.Format("{0}.{1} (value '{2}') is not a number or list of numbers", path, text, (item.Value is string) ? item.Value : (item.Value?.GetType().Name ?? "null")));
						continue;
					}
					array2 = array3;
				}
				if (flag2)
				{
					flag = true;
					array = array2;
				}
				else
				{
					dictionary[result] = array2;
				}
			}
			if (flag)
			{
				int undefined = allEnumValues.Count - dictionary.Count;
				if (array != null && undefined > 0)
				{
					array = array.Select((float p) => p / (float)undefined).ToArray();
				}
				foreach (T allEnumValue in allEnumValues)
				{
					if (!dictionary.ContainsKey(allEnumValue))
					{
						dictionary[allEnumValue] = array;
					}
				}
			}
			return dictionary;
		}
	}
}
namespace ConfigReload
{
	internal static class ConfigReloader
	{
		private sealed class Poller : IDisposable
		{
			private const int IntervalMilliseconds = 5000;

			private readonly ConfigFile config;

			private readonly ManualLogSource? log;

			private readonly string fileName;

			private readonly Timer timer;

			private DateTime lastWriteTime;

			private long lastLength;

			private string lastContent;

			private int reloading;

			public Poller(ConfigFile config, ManualLogSource? log)
			{
				this.config = config;
				this.log = log;
				fileName = Path.GetFileName(config.ConfigFilePath);
				Snapshot(out lastWriteTime, out lastLength);
				lastContent = ReadOrNull(config.ConfigFilePath) ?? "";
				timer = new Timer(delegate
				{
					Poll();
				}, null, 5000, 5000);
			}

			private void Poll()
			{
				try
				{
					Snapshot(out var writeTime, out var length);
					if (writeTime == lastWriteTime && length == lastLength)
					{
						return;
					}
					lastWriteTime = writeTime;
					lastLength = length;
					string text = ReadOrNull(config.ConfigFilePath);
					if (text != null && !(text == lastContent))
					{
						lastContent = text;
						if (Interlocked.CompareExchange(ref reloading, 1, 0) == 0)
						{
							ThreadingHelper.SynchronizingObject.BeginInvoke(new Action(Reload), null);
						}
					}
				}
				catch (Exception ex)
				{
					ManualLogSource? obj = log;
					if (obj != null)
					{
						obj.LogError((object)("Watching " + fileName + " failed: " + ex.Message));
					}
				}
			}

			private void Reload()
			{
				try
				{
					ManualLogSource? obj = log;
					if (obj != null)
					{
						obj.LogInfo((object)(fileName + " changed on disk, reloading"));
					}
					config.Reload();
				}
				catch (Exception ex)
				{
					ManualLogSource? obj2 = log;
					if (obj2 != null)
					{
						obj2.LogError((object)("Failed to reload " + fileName + ", please check it for typos and formatting: " + ex.Message));
					}
				}
				finally
				{
					Interlocked.Exchange(ref reloading, 0);
				}
			}

			private void Snapshot(out DateTime writeTime, out long length)
			{
				FileInfo fileInfo = new FileInfo(config.ConfigFilePath);
				if (fileInfo.Exists)
				{
					writeTime = fileInfo.LastWriteTimeUtc;
					length = fileInfo.Length;
				}
				else
				{
					writeTime = DateTime.MinValue;
					length = -1L;
				}
			}

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

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

		public static IDisposable Setup(ConfigFile config, ManualLogSource? log = null, bool saveNow = true)
		{
			if (saveNow)
			{
				config.Save();
			}
			return new Poller(config, log);
		}
	}
}
namespace ServerSync
{
	[PublicAPI]
	public abstract class OwnConfigEntryBase
	{
		public object? LocalBaseValue;

		public bool SynchronizedConfig = true;

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

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

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

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

		public readonly string Identifier;

		public readonly Type Type;

		private object? boxedValue;

		protected bool localIsOwner;

		public readonly int Priority;

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

		public event Action? ValueChanged;

		protected CustomSyncedValueBase(ConfigSync configSync, string identifier, Type type, int priority)
		{
			Priority = priority;
			Identifier = identifier;
			Type = type;
			configSync.AddCustomValue(this);
			localIsOwner = configSync.IsSourceOfTruth;
			configSync.SourceOfTruthChanged += delegate(bool truth)
			{
				localIsOwner = truth;
			};
		}
	}
	[PublicAPI]
	public 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), int priority = 0)
			: base(configSync, identifier, typeof(T), priority)
		{
			Value = value;
		}

		public void AssignLocalValue(T value)
		{
			if (localIsOwner)
			{
				Value = value;
			}
			else
			{
				LocalBaseValue = value;
			}
		}
	}
	internal class ConfigurationManagerAttributes
	{
		[UsedImplicitly]
		public bool? ReadOnly = false;
	}
	[PublicAPI]
	public 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()
				{
					MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null);
					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 = ZNet.instance.GetPeers().Where(delegate(ZNetPeer p)
							{
								string hostName = p.m_rpc.GetSocket().GetHostName();
								return ((object)listContainsId != null) ? ((bool)listContainsId.Invoke(ZNet.instance, new object[2] { adminList, hostName })) : adminList.Contains(hostName);
							}).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 : ZPlayFabSocket, 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;
					((ZPlayFabSocket)this)..ctor();
				}

				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())
				{
					return;
				}
				BufferingSocket bufferingSocket = new BufferingSocket(rpc.GetSocket());
				AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket);
				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)
				{
					FieldInfo fieldInfo = AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket");
					object? value = fieldInfo.GetValue(val);
					ZPlayFabSocket val2 = (ZPlayFabSocket)((value is ZPlayFabSocket) ? value : null);
					if (val2 != null)
					{
						typeof(ZPlayFabSocket).GetField("m_remotePlayerId").SetValue(bufferingSocket, val2.m_remotePlayerId);
					}
					fieldInfo.SetValue(val, bufferingSocket);
				}
				if (__state == null)
				{
					__state = new Dictionary<Assembly, BufferingSocket>();
				}
				__state[Assembly.GetExecutingAssembly()] = bufferingSocket;
			}

			[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), "SetSer