Decompiled source of Lockstep v0.3.0

plugins\Lockstep.dll

Decompiled 7 hours 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 Charter;
using ConfigReload;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using PatchGuard;
using SyncedConfig;
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("Lockstep")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Lockstep: boss progression gated on the whole group")]
[assembly: AssemblyFileVersion("0.3.0.0")]
[assembly: AssemblyInformationalVersion("0.3.0+6bfa7e58761853b59c3962d1144cef1b935b57e4")]
[assembly: AssemblyProduct("Lockstep")]
[assembly: AssemblyTitle("Lockstep")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.3.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace Lockstep
{
	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();
			Lockstep.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 : YamlModel
	{
		private readonly List<Stage> stages = new List<Stage>();

		protected override void Read(YamlNode root)
		{
			foreach (KeyValuePair<string, YamlNode> entry in root.Entries)
			{
				ReadStage(entry.Key, entry.Value);
			}
		}

		private void ReadStage(string name, YamlNode node)
		{
			node.Get("order").TryInt(out var result);
			if (!node.Get("key").TryString(out string result2) || string.IsNullOrEmpty(result2))
			{
				node.Error("missing 'key', the global key the game sets when the boss dies");
			}
			if (!node.Get("boss").TryString(out string result3) || string.IsNullOrEmpty(result3))
			{
				node.Error("missing 'boss', the prefab name of the boss the altar spawns");
			}
			stages.Add(new Stage
			{
				Name = name,
				Key = result2,
				BossPrefab = result3,
				Order = result
			});
		}

		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)
			{
				base.Warnings.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)
			{
				base.Errors.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 (!LockstepConfiguration.SpawnGuard.Value || !AltarGate.IsClosed(__instance, null))
			{
				return true;
			}
			Lockstep.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);
				}
			}
			Lockstep.Log.LogInfo((object)(__instance.m_name + " died, attackers: " + ((list.Count > 0) ? string.Join(", ", list) : "none recorded") + "."));
			ZRoutedRpc.instance.InvokeRoutedRPC("Lockstep_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 Article<string> article;

		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)
		{
			article = new Article<string>(config.Sync, "lockstep_state", "", standing: true);
			article.Changed += delegate
			{
				Guard.Run("progress state changed", Parse);
			};
		}

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

		private static void Parse()
		{
			byBoss.Clear();
			string[] array = (article.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 = (LockstepConfiguration.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 "Lockstep: no progression state received from the server yet.";
			}
			return "Lockstep 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("lockstep 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 = "lockstep 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("lockstep 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("lockstep", "lockstep 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("Lockstep: 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("Lockstep_Command", new object[1] { text });
		}

		public static void Print(string text)
		{
			if ((Object)(object)Console.instance == (Object)null)
			{
				Lockstep.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())
			{
				Lockstep.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.Lockstep", "Lockstep", "0.3.0")]
	public class Lockstep : BaseUnityPlugin
	{
		public const string PluginGuid = "com.Lockstep";

		public const string PluginName = "Lockstep";

		public const string PluginVersion = "0.3.0";

		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, "Lockstep", "0.3.0");
			LockstepConfiguration.Initialize(Synced);
			ProgressState.Initialize(Synced);
			Chain.Changed += ProgressServer.Publish;
			Harmony val = new Harmony("com.Lockstep");
			val.PatchAll(Assembly.GetExecutingAssembly());
			Synced.Finish(val);
			Guard.Install(val, ((BaseUnityPlugin)this).Logger, Assembly.GetExecutingAssembly());
			Log.LogInfo((object)"Lockstep 0.3.0 loaded.");
		}
	}
	public enum LateJoinerMode
	{
		Catchup,
		Earn
	}
	public static class LockstepConfiguration
	{
		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 YamlFileSet ChainSource { get; private set; }

		public static void Initialize(SyncedConfiguration config)
		{
			BindGeneral(config);
			BindCredit(config);
			BindGroup(config);
			BindGate(config);
			AddChain(config);
			RepublishOnGroupChange(config);
		}

		private static void BindGeneral(SyncedConfiguration config)
		{
			LockConfiguration = config.BindLocking("General", "Lock Configuration", defaultValue: true, "Server only. When on, every player uses the server's values for this file and cannot override them locally.");
		}

		private static void BindCredit(SyncedConfiguration config)
		{
			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);
		}

		private static void BindGroup(SyncedConfiguration config)
		{
			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);
		}

		private static void BindGate(SyncedConfiguration config)
		{
			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);
		}

		private static void AddChain(SyncedConfiguration config)
		{
			ChainSource = config.AddYaml(new YamlFileSet("LockstepChain*.yml", "lockstepchain", () => new ChainDocument(), delegate(YamlModel model)
			{
				Chain.Set(((ChainDocument)model).Process());
			})
			{
				DefaultContent = SyncedConfiguration.EmbeddedResource(Assembly.GetExecutingAssembly(), "Lockstep.LockstepChain.yml"),
				EditorLabel = () => "Edit progression chain"
			});
		}

		private static void RepublishOnGroupChange(SyncedConfiguration config)
		{
			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
	{
		internal struct OnlinePlayer
		{
			public long Id;

			public string Name;

			public Vector3 Position;
		}

		public const string RpcBossDefeated = "Lockstep_BossDefeated";

		public const string RpcCommand = "Lockstep_Command";

		public const string RpcReply = "Lockstep_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>("Lockstep_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>("Lockstep_Command", (Action<long, string>)delegate(long sender, string line)
			{
				Guard.Run("command", delegate
				{
					ServerCommands.OnCommand(sender, line);
				});
			});
			ZRoutedRpc.instance.Register<string>("Lockstep_Reply", (Action<long, string>)delegate(long sender, string text)
			{
				Guard.Run("reply", delegate
				{
					Commands.Print(text);
				});
			});
		}

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

		internal 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();
				Lockstep.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 && LockstepConfiguration.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)
				{
					Lockstep.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();
			}
		}

		internal 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_0035: Unknown result type (might be due to invalid IL or missing references)
			if (IsServer)
			{
				Stage stage = Chain.ByKey(key);
				if (stage == null)
				{
					Lockstep.Log.LogInfo((object)("A boss with key " + key + " died; it is not in the chain."));
					return;
				}
				Roster obj = EnsureRoster();
				List<string> list = CreditPlayers(obj, key, attackers, position);
				obj.Save();
				Lockstep.Log.LogInfo((object)(stage.Name + " defeated. Credited: " + ((list.Count > 0) ? string.Join(", ", list) : "nobody new") + "."));
				Publish();
			}
		}

		private static List<string> CreditPlayers(Roster r, string key, string attackers, Vector3 position)
		{
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			HashSet<string> attackerNames = new HashSet<string>(from n in attackers.Split(new char[1] { '\n' })
				where n.Length > 0
				select n, StringComparer.Ordinal);
			List<string> list = new List<string>();
			foreach (OnlinePlayer item in Online())
			{
				if (EarnedCredit(item, attackerNames, position))
				{
					bool isNew;
					RosterEntry rosterEntry = r.Touch(item.Id, item.Name, out isNew);
					if (!rosterEntry.Cleared.Contains(key))
					{
						rosterEntry.Cleared.Add(key);
						list.Add(item.Name);
					}
				}
			}
			return list;
		}

		private static bool EarnedCredit(OnlinePlayer player, HashSet<string> attackerNames, Vector3 position)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			if (LockstepConfiguration.CreditEveryoneOnline.Value || attackerNames.Contains(player.Name))
			{
				return true;
			}
			float value = LockstepConfiguration.CreditRadius.Value;
			if (value > 0f)
			{
				return Vector3.Distance(player.Position, position) <= value;
			}
			return false;
		}

		internal static bool Counts(RosterEntry entry, HashSet<long> online)
		{
			if (entry.Ignored)
			{
				return false;
			}
			if (online.Contains(entry.Id))
			{
				return true;
			}
			if (LockstepConfiguration.CountOnlyOnline.Value)
			{
				return false;
			}
			int value = LockstepConfiguration.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());
		}
	}
	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, "Lockstep." + 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 "# Lockstep 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 Lockstep was installed\n# Console: lockstep status | grant | revoke | ignore | unignore | forget\n";
		}

		private static RosterFile Parse(string text)
		{
			try
			{
				return deserializer.Deserialize<RosterFile>(text);
			}
			catch (Exception ex)
			{
				Lockstep.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
			{
				PollOnce();
			}
			catch (IOException)
			{
			}
			catch (Exception ex2)
			{
				Lockstep.Log.LogError((object)("Watching the roster file failed: " + ex2.Message));
			}
		}

		private void PollOnce()
		{
			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);
			}
		}

		private void Reload(string text)
		{
			try
			{
				RosterFile rosterFile = Parse(text);
				if (rosterFile != null)
				{
					Data = rosterFile;
					lastWrittenText = text;
					Lockstep.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;
		}
	}
	public static class ServerCommands
	{
		private const string Usage = "Usage: lockstep status | grant <player> <stage> | revoke <player> <stage> | ignore <player> | unignore <player> | forget <player>";

		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, "Lockstep_Reply", new object[1] { text });
		}

		public static void OnCommand(long sender, string line)
		{
			if (ProgressServer.IsServer)
			{
				string[] array = line.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
				string text = ((array.Length != 0) ? array[0].ToLowerInvariant() : "status");
				if (text == "status")
				{
					Reply(sender, Status());
				}
				else if (!IsAdmin(sender))
				{
					Reply(sender, "Lockstep: only admins can change the roster.");
				}
				else
				{
					Reply(sender, RunAdminVerb(text, array));
				}
			}
		}

		private static string RunAdminVerb(string verb, string[] args)
		{
			return verb switch
			{
				"grant" => Grant(args), 
				"revoke" => Revoke(args), 
				"ignore" => Ignore(args), 
				"unignore" => Unignore(args), 
				"forget" => Forget(args), 
				_ => "Usage: lockstep status | grant <player> <stage> | revoke <player> <stage> | ignore <player> | unignore <player> | forget <player>", 
			};
		}

		private static string Grant(string[] args)
		{
			RosterEntry entry;
			Stage stage;
			string text = FindPlayerAndStage("grant", args, out entry, out stage);
			if (text != null)
			{
				return text;
			}
			if (!entry.Cleared.Contains(stage.Key))
			{
				entry.Cleared.Add(stage.Key);
			}
			SaveAndPublish();
			return entry.Name + ": " + stage.Name + " granted.";
		}

		private static string Revoke(string[] args)
		{
			RosterEntry entry;
			Stage stage;
			string text = FindPlayerAndStage("revoke", args, out entry, out stage);
			if (text != null)
			{
				return text;
			}
			entry.Cleared.Remove(stage.Key);
			SaveAndPublish();
			return entry.Name + ": " + stage.Name + " revoked.";
		}

		private static string Ignore(string[] args)
		{
			return SetIgnored("ignore", args, ignored: true);
		}

		private static string Unignore(string[] args)
		{
			return SetIgnored("unignore", args, ignored: false);
		}

		private static string SetIgnored(string verb, string[] args, bool ignored)
		{
			RosterEntry entry;
			string text = FindPlayer(verb, args, out entry);
			if (text != null)
			{
				return text;
			}
			entry.Ignored = ignored;
			SaveAndPublish();
			return entry.Name + " is " + (entry.Ignored ? "now ignored" : "counted again") + ".";
		}

		private static string Forget(string[] args)
		{
			RosterEntry entry;
			string text = FindPlayer("forget", args, out entry);
			if (text != null)
			{
				return text;
			}
			ProgressServer.EnsureRoster().Data.Players.Remove(entry);
			SaveAndPublish();
			return entry.Name + " removed from the roster.";
		}

		private static string FindPlayer(string verb, string[] args, out RosterEntry entry)
		{
			entry = null;
			if (args.Length < 2)
			{
				return "Usage: lockstep " + verb + " <player>";
			}
			entry = ProgressServer.EnsureRoster().Find(args[1]);
			if (entry != null)
			{
				return null;
			}
			return "No player '" + args[1] + "' in the roster.";
		}

		private static string FindPlayerAndStage(string verb, string[] args, out RosterEntry entry, out Stage stage)
		{
			entry = null;
			stage = null;
			if (args.Length < 3)
			{
				return "Usage: lockstep " + verb + " <player> <stage>";
			}
			entry = ProgressServer.EnsureRoster().Find(args[1]);
			stage = Chain.Find(args[2]);
			if (entry == null)
			{
				return "No player '" + args[1] + "' in the roster.";
			}
			if (stage != null)
			{
				return null;
			}
			return "No stage '" + args[2] + "' in the chain.";
		}

		private static void SaveAndPublish()
		{
			ProgressServer.EnsureRoster().Save();
			ProgressServer.Publish();
		}

		private static string Status()
		{
			Roster roster = ProgressServer.EnsureRoster();
			HashSet<long> online = new HashSet<long>(from p in ProgressServer.Online()
				select p.Id);
			StringBuilder stringBuilder = new StringBuilder("Lockstep status\n");
			foreach (Stage stage in Chain.Stages)
			{
				List<string> list = ProgressServer.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 player in roster.Data.Players)
			{
				AppendPlayer(stringBuilder, player, online);
			}
			return stringBuilder.ToString().TrimEnd(new char[1] { '\n' });
		}

		private static void AppendPlayer(StringBuilder text, RosterEntry entry, HashSet<long> online)
		{
			string value = (entry.Ignored ? "ignored" : (online.Contains(entry.Id) ? "online" : (ProgressServer.Counts(entry, online) ? "counted" : "inactive")));
			string text2 = string.Join(", ", from s in Chain.Stages
				where entry.Cleared.Contains(s.Key)
				select s.Name);
			text.Append("  ").Append(entry.Name).Append(" (")
				.Append(entry.Id)
				.Append(") ")
				.Append(value)
				.Append(", last seen ")
				.Append(entry.LastSeen)
				.Append(", cleared: ")
				.Append((text2.Length > 0) ? text2 : "none")
				.Append('\n');
		}
	}
}
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 global::Charter.Charter Sync { get; }

		public ConfigFile Config { get; }

		public ManualLogSource Log { get; }

		public YamlFileHub Yaml { get; }

		public YamlEditorWindow YamlEditor { get; }

		public IReadOnlyList<string> SearchPaths { get; }

		public IDisposable? ConfigWatcher { get; private set; }

		public bool IsLocked
		{
			get
			{
				if (Sync.IsBound)
				{
					return !Sync.MayAmend;
				}
				return false;
			}
		}

		public bool IsAdmin => Sync.IsSteward;

		public bool IsAuthor => Sync.IsAuthor;

		public SyncedConfiguration(BaseUnityPlugin plugin, ManualLogSource log, string title, string version, string? oldestAccepted = null, bool mandatory = true)
		{
			Config = plugin.Config;
			Log = log;
			string gUID = plugin.Info.Metadata.GUID;
			Sync = new global::Charter.Charter(gUID, title, version, oldestAccepted, mandatory);
			SearchPaths = new string[2]
			{
				Path.GetDirectoryName(Config.ConfigFilePath) ?? Paths.ConfigPath,
				Path.GetDirectoryName(((object)plugin).GetType().Assembly.Location) ?? Paths.PluginPath
			};
			Yaml = new YamlFileHub(title, Log, SearchPaths, Sync);
			YamlEditor = new YamlEditorWindow(Yaml, title + " 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.Clause<T>(val, !synced);
			return val;
		}

		public Clause<T> Add<T>(ConfigEntry<T> entry, bool synced = true)
		{
			return Sync.Clause<T>(entry, !synced);
		}

		public ConfigEntry<bool> BindLocking(string section, string key, bool defaultValue, string description)
		{
			ConfigEntry<bool> val = Config.Bind<bool>(section, key, defaultValue, description);
			Sync.Binding(val);
			return val;
		}

		public YamlFileSet AddYaml(YamlFileSet set)
		{
			return Yaml.Register(set);
		}

		public void Finish(Harmony harmony, int yamlApplyPriority = 400)
		{
			global::Charter.Charter.Install(harmony);
			ConfigWatcher = ConfigReloader.Setup(Config, Log);
			if (Yaml.Sets.Count > 0)
			{
				Yaml.HookGame(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 YamlEditorWindow
	{
		private const float ValidationHeight = 100f;

		private readonly YamlFileHub hub;

		private readonly string windowTitle;

		private readonly List<string> paths = new List<string>();

		private readonly List<string> texts = new List<string>();

		private readonly List<bool> expanded = new List<bool>();

		private readonly List<Vector2> scrolls = new List<Vector2>();

		private readonly List<string> errors = new List<string>();

		private readonly List<string> warnings = new List<string>();

		private Vector2 validationScroll;

		private YamlFileSet? set;

		private Texture2D? background;

		private GUIStyle? errorStyle;

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

		public bool IsOpen => set != null;

		private Texture2D Background
		{
			get
			{
				//IL_0011: Unknown result type (might be due to invalid IL or missing references)
				//IL_001b: Expected O, but got Unknown
				//IL_0037: Unknown result type (might be due to invalid IL or missing references)
				if ((Object)(object)background == (Object)null)
				{
					background = new Texture2D(1, 1);
					background.SetPixel(0, 0, new Color(0f, 0f, 0f, 0.9f));
					background.Apply();
				}
				return background;
			}
		}

		private GUIStyle ErrorStyle
		{
			get
			{
				//IL_0015: Unknown result type (might be due to invalid IL or missing references)
				//IL_001a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0020: Unknown result type (might be due to invalid IL or missing references)
				//IL_002a: Unknown result type (might be due to invalid IL or missing references)
				//IL_002c: Expected O, but got Unknown
				//IL_0031: Expected O, but got Unknown
				GUIStyle obj = errorStyle;
				if (obj == null)
				{
					GUIStyle val = new GUIStyle(GUI.skin.label);
					val.normal.textColor = Color.red;
					GUIStyle val2 = val;
					errorStyle = val;
					obj = val2;
				}
				return obj;
			}
		}

		public YamlEditorWindow(YamlFileHub hub, string windowTitle)
		{
			this.hub = hub;
			this.windowTitle = windowTitle;
		}

		public void Open(YamlFileSet set)
		{
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			this.set = set;
			paths.Clear();
			texts.Clear();
			expanded.Clear();
			scrolls.Clear();
			foreach (KeyValuePair<string, string> file in set.Files)
			{
				paths.Add(file.Key);
				texts.Add(file.Value);
				expanded.Add(item: true);
				scrolls.Add(Vector2.zero);
			}
			Validate();
		}

		public void Close()
		{
			set = null;
		}

		public void DrawButtons()
		{
			foreach (YamlFileSet item in hub.Sets.Where((YamlFileSet s) => s.Files.Count > 0))
			{
				if (GUILayout.Button(Translate(item.EditorLabel()), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }))
				{
					Open(item);
				}
			}
		}

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

		public void OnGUI()
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			if (set != null)
			{
				HandleEditingKeys();
				GUI.DrawTexture(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), (Texture)(object)Background);
				GUILayout.BeginArea(new Rect(20f, 20f, (float)(Screen.width - 40), (float)(Screen.height - 40)));
				GUILayout.Label(Translate(windowTitle), Array.Empty<GUILayoutOption>());
				DrawTopRow();
				for (int i = 0; i < texts.Count; i++)
				{
					DrawFile(i);
				}
				DrawValidation();
				GUILayout.EndArea();
			}
		}

		private void DrawTopRow()
		{
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUI.enabled = hub.IsAuthor && errors.Count == 0;
			if (GUILayout.Button(Translate("Save and apply"), Array.Empty<GUILayoutOption>()))
			{
				Submit(saveToDisk: true);
			}
			if (GUILayout.Button(Translate("Apply without saving"), Array.Empty<GUILayoutOption>()))
			{
				Submit(saveToDisk: false);
			}
			GUI.enabled = true;
			if (GUILayout.Button(Translate("Discard"), Array.Empty<GUILayoutOption>()))
			{
				Close();
			}
			GUILayout.EndHorizontal();
		}

		private void Submit(bool saveToDisk)
		{
			if (set != null)
			{
				Dictionary<string, string> dictionary = new Dictionary<string, string>();
				for (int i = 0; i < paths.Count; i++)
				{
					dictionary[paths[i]] = texts[i];
				}
				hub.Replace(set, dictionary, saveToDisk);
				Close();
			}
		}

		private void DrawFile(int index)
		{
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			if (texts.Count > 1)
			{
				expanded[index] = GUILayout.Toggle(expanded[index], Path.GetFileName(paths[index]), Array.Empty<GUILayoutOption>());
				if (!expanded[index])
				{
					return;
				}
			}
			scrolls[index] = GUILayout.BeginScrollView(scrolls[index], (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) });
			GUI.SetNextControlName(ControlName(index));
			string text = GUILayout.TextArea(texts[index], (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) });
			GUILayout.EndScrollView();
			if (text != texts[index])
			{
				texts[index] = text;
				Validate();
			}
		}

		private void DrawValidation()
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			validationScroll = GUILayout.BeginScrollView(validationScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(100f) });
			if (errors.Count == 0 && warnings.Count == 0)
			{
				GUILayout.Label(Translate("Configuration is valid."), Array.Empty<GUILayoutOption>());
			}
			foreach (string error in errors)
			{
				GUILayout.Label(error, ErrorStyle, Array.Empty<GUILayoutOption>());
			}
			foreach (string warning in warnings)
			{
				GUILayout.Label(warning, Array.Empty<GUILayoutOption>());
			}
			GUILayout.EndScrollView();
		}

		private void Validate()
		{
			errors.Clear();
			warnings.Clear();
			if (set != null)
			{
				YamlModel yamlModel = set.CreateModel();
				yamlModel.LoadAll(paths.Select((string path, int i) => new KeyValuePair<string, string>(path, texts[i])));
				errors.AddRange(yamlModel.Errors);
				warnings.AddRange(yamlModel.Warnings);
			}
		}

		private void HandleEditingKeys()
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Invalid comparison between Unknown and I4
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Invalid comparison between Unknown and I4
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Invalid comparison between Unknown and I4
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Invalid comparison between Unknown and I4
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Expected O, but got Unknown
			Event current = Event.current;
			if ((int)current.type == 4)
			{
				bool flag = (int)current.keyCode == 9 || current.character == '\t';
				bool flag2 = (int)current.keyCode == 13 || (int)current.keyCode == 271 || current.character == '\n';
				int num = FocusedFile();
				if (num >= 0 && (flag || flag2))
				{
					TextEditor val = (TextEditor)GUIUtility.GetStateObject(typeof(TextEditor), GUIUtility.keyboardControl);
					val.ReplaceSelection(flag ? "  " : ("\n" + IndentationOfLineAt(val.text, val.cursorIndex)));
					texts[num] = val.text;
					current.Use();
					Validate();
				}
			}
		}

		private int FocusedFile()
		{
			string nameOfFocusedControl = GUI.GetNameOfFocusedControl();
			for (int i = 0; i < texts.Count; i++)
			{
				if (nameOfFocusedControl == ControlName(i))
				{
					return i;
				}
			}
			return -1;
		}

		private static string IndentationOfLineAt(string text, int position)
		{
			int num = Math.Max(0, Math.Min(position, text.Length));
			while (num > 0 && text[num - 1] != '\n')
			{
				num--;
			}
			int i;
			for (i = num; i < text.Length && text[i] == ' '; i++)
			{
			}
			return text.Substring(num, i - num);
		}

		private static string ControlName(int index)
		{
			return "YamlEditorWindow.File" + index;
		}
	}
	public sealed class YamlFileHub
	{
		private readonly string modName;

		private readonly ManualLogSource log;

		private readonly global::Charter.Charter? charter;

		private readonly List<YamlFileSet> sets = new List<YamlFileSet>();

		private readonly YamlFileStore store;

		private readonly YamlFileWatcher watcher;

		private bool loaded;

		public IReadOnlyList<YamlFileSet> Sets => sets;

		public bool IsAuthor => charter?.IsAuthor ?? true;

		public bool IsLocked
		{
			get
			{
				if (charter != null && charter.IsBound)
				{
					return !charter.MayAmend;
				}
				return false;
			}
		}

		public bool SuppressWriteBack { get; set; }

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

		public event Action<YamlFileSet>? Applied;

		public YamlFileHub(string modName, ManualLogSource log, IEnumerable<string> searchFolders, global::Charter.Charter? charter)
		{
			this.modName = modName;
			this.log = log;
			this.charter = charter;
			store = new YamlFileStore(modName, log, searchFolders);
			watcher = new YamlFileWatcher(store, sets, ReloadFromDisk, log, modName);
		}

		public YamlFileSet Register(YamlFileSet set)
		{
			if (sets.Any((YamlFileSet s) => string.Equals(s.SyncKey, set.SyncKey, StringComparison.OrdinalIgnoreCase)))
			{
				throw new ArgumentException(modName + ": a YAML file set with sync key '" + set.SyncKey + "' is already registered");
			}
			sets.Add(set);
			if (charter != null)
			{
				Article<List<string>> channel = new Article<List<string>>(charter, set.SyncKey, new List<string>());
				set.Channel = channel;
				channel.Changed += delegate
				{
					Receive(set, YamlFileList.Unflatten(channel.Value));
				};
			}
			return set;
		}

		public void LoadAll()
		{
			loaded = true;
			foreach (YamlFileSet set in sets)
			{
				LoadSet(set);
			}
			watcher.Start();
		}

		public void ApplyAll()
		{
			if (!CanApply())
			{
				log.LogDebug((object)(modName + ": YAML apply skipped, the game is not ready"));
				return;
			}
			foreach (YamlFileSet set in sets)
			{
				Apply(set);
			}
		}

		public bool TryBuild(YamlFileSet set, IReadOnlyDictionary<string, string> files, out YamlModel model, string context)
		{
			model = set.CreateModel();
			model.LoadAll(files);
			foreach (string warning in model.Warnings)
			{
				log.LogWarning((object)(modName + ": " + warning));
			}
			if (model.Errors.Count == 0)
			{
				return true;
			}
			log.LogError((object)$"{modName}: {set.MainFileName} ({context}) has {model.Errors.Count} error(s), the previous configuration stays:");
			foreach (string error in model.Errors)
			{
				log.LogError((object)(modName + ": " + error));
			}
			return false;
		}

		public void Replace(YamlFileSet set, IReadOnlyDictionary<string, string> files, bool saveToDisk)
		{
			if (TryBuild(set, files, out YamlModel _, "editor"))
			{
				Publish(set, files, saveToDisk);
			}
		}

		public void HookGame(Harmony harmony, int applyPriority)
		{
			YamlGameHooks.Install(this, harmony, applyPriority);
		}

		internal void LoadFromHook()
		{
			if (loaded)
			{
				return;
			}
			try
			{
				LoadAll();
			}
			catch (Exception arg)
			{
				log.LogError((object)$"{modName}: loading YAML files failed: {arg}");
			}
		}

		internal void ApplyFromHook()
		{
			try
			{
				ApplyAll();
			}
			catch (Exception arg)
			{
				log.LogError((object)$"{modName}: applying YAML files failed: {arg}");
			}
		}

		private void LoadSet(YamlFileSet set)
		{
			Dictionary<string, string> dictionary = store.ReadOrCreate(set);
			watcher.TakeSnapshot(set);
			YamlModel model;
			if (dictionary.Count == 0)
			{
				if (set.Enabled())
				{
					log.LogWarning((object)(modName + ": no " + set.FilePattern + " found in " + string.Join(" or ", store.SearchFolders)));
				}
			}
			else if (TryBuild(set, dictionary, out model, "startup"))
			{
				Publish(set, dictionary, writeBack: false);
			}
		}

		private void ReloadFromDisk(YamlFileSet set)
		{
			Dictionary<string, string> dictionary = store.Read(store.Discover(set));
			watcher.TakeSnapshot(set);
			YamlModel model;
			if (dictionary.Count == 0)
			{
				log.LogWarning((object)(modName + ": every " + set.FilePattern + " file is gone, keeping the loaded configuration"));
			}
			else if (TryBuild(set, dictionary, out model, "reload"))
			{
				Publish(set, dictionary, writeBack: false);
				log.LogInfo((object)(modName + ": " + set.MainFileName + " reloaded" + (IsAuthor ? "" : ", not applied, remote configuration active")));
			}
		}

		private void Publish(YamlFileSet set, IReadOnlyDictionary<string, string> files, bool writeBack)
		{
			bool suppressWriteBack = SuppressWriteBack;
			SuppressWriteBack = suppressWriteBack || !writeBack;
			try
			{
				if (set.Channel == null)
				{
					Receive(set, files);
				}
				else
				{
					set.Channel.Assign(YamlFileList.Flatten(files));
				}
			}
			finally
			{
				SuppressWriteBack = suppressWriteBack;
			}
		}

		private void Receive(YamlFileSet set, IReadOnlyDictionary<string, string> files)
		{
			if (!TryBuild(set, files, out YamlModel model, "received"))
			{
				foreach (KeyValuePair<string, string> file in files)
				{
					log.LogWarning((object)(modName + ": content of " + file.Key + ":\n" + file.Value));
				}
				return;
			}
			set.Files = files;
			set.Current = model;
			if (CanApply())
			{
				Apply(set);
			}
			if (IsAuthor && !SuppressWriteBack)
			{
				store.WriteAll(files);
				watcher.TakeSnapshot(set);
			}
		}

		private void Apply(YamlFileSet set)
		{
			if (set.Current == null || !set.Enabled())
			{
				return;
			}
			try
			{
				set.Apply(set.Current);
				this.Applied?.Invoke(set);
			}
			catch (Exception arg)
			{
				log.LogError((object)$"{modName}: applying {set.MainFileName} failed: {arg}");
			}
		}
	}
	internal static class YamlFileList
	{
		public static List<string> Flatten(IReadOnlyDictionary<string, string> files)
		{
			List<string> list = new List<string>(files.Count * 2);
			foreach (KeyValuePair<string, string> file in files)
			{
				list.Add(file.Key);
				list.Add(file.Value);
			}
			return list;
		}

		public static Dictionary<string, string> Unflatten(List<string>? list)
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>();
			int num = 0;
			while (list != null && num + 1 < list.Count)
			{
				dictionary[list[num]] = list[num + 1];
				num += 2;
			}
			return dictionary;
		}
	}
	public sealed class YamlFileSet
	{
		internal int ReloadPending;

		public string FilePattern { get; }

		public string MainFileName { get; }

		public string SyncKey { get; }

		public Func<YamlModel> CreateModel { get; }

		public Action<YamlModel> Apply { get; }

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

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

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

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

		public YamlModel? Current { get; internal set; }

		internal Article<List<string>>? Channel { get; set; }

		internal Dictionary<string, DateTime> Snapshot { get; } = new Dictionary<string, DateTime>(StringComparer.OrdinalIgnoreCase);

		public YamlFileSet(string filePattern, string syncKey, Func<YamlModel> createModel, Action<YamlModel> apply)
		{
			FilePattern = filePattern;
			MainFileName = filePattern.Replace("*", "");
			SyncKey = syncKey;
			CreateModel = createModel;
			Apply = apply;
			EditorLabel = () => "Edit " + MainFileName;
		}
	}
	internal sealed class YamlFileStore
	{
		private readonly string modName;

		private readonly ManualLogSource log;

		private readonly List<string> searchFolders;

		public IReadOnlyList<string> SearchFolders => searchFolders;

		public YamlFileStore(string modName, ManualLogSource log, IEnumerable<string> searchFolders)
		{
			this.modName = modName;
			this.log = log;
			this.searchFolders = searchFolders.ToList();
		}

		public List<string> Discover(YamlFileSet set)
		{
			Regex nameFilter = new Regex("^" + Regex.Escape(set.FilePattern).Replace("\\*", ".*") + "$", RegexOptions.IgnoreCase);
			List<string> list = new List<string>();
			foreach (string item in searchFolders.Where(Directory.Exists))
			{
				list.AddRange(from p in Directory.GetFiles(item, set.FilePattern)
					where nameFilter.IsMatch(Path.GetFileName(p))
					select p);
			}
			return list.OrderBy((string p) => (!string.Equals(Path.GetFileName(p), set.MainFileName, StringComparison.OrdinalIgnoreCase)) ? 1 : 0).ThenBy<string, string>(Path.GetFileName, StringComparer.OrdinalIgnoreCase).ToList();
		}

		public Dictionary<string, string> Read(IEnumerable<string> paths)
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>();
			foreach (string path in paths)
			{
				try
				{
					dictionary[path] = File.ReadAllText(path);
				}
				catch (Exception ex)
				{
					log.LogError((object)(modName + ": reading " + path + " failed: " + ex.Message));
				}
			}
			return dictionary;
		}

		public Dictionary<string, string> ReadOrCreate(YamlFileSet set)
		{
			Dictionary<string, string> dictionary = Read(Discover(set));
			if (dictionary.Count <= 0)
			{
				return WriteDefault(set);
			}
			return dictionary;
		}

		public Dictionary<string, DateTime> WriteTimes(YamlFileSet set)
		{
			Dictionary<string, DateTime> dictionary = new Dictionary<string, DateTime>(StringComparer.OrdinalIgnoreCase);
			foreach (string item in Discover(set))
			{
				dictionary[item] = File.GetLastWriteTimeUtc(item);
			}
			return dictionary;
		}

		public void WriteAll(IReadOnlyDictionary<string, string> files)
		{
			foreach (KeyValuePair<string, string> file in files)
			{
				WriteAtomic(file.Key, file.Value);
			}
		}

		private Dictionary<string, string> WriteDefault(YamlFileSet set)
		{
			byte[] array = set.DefaultContent?.Invoke();
			if (array == null || searchFolders.Count == 0)
			{
				return new Dictionary<string, string>();
			}
			string text = Path.Combine(searchFolders[0], set.MainFileName);
			try
			{
				Directory.CreateDirectory(searchFolders[0]);
				File.WriteAllBytes(text, array);
				log.LogInfo((object)(modName + ": wrote default " + text));
			}
			catch (Exception ex)
			{
				log.LogError((object)(modName + ": writing default " + text + " failed: " + ex.Message));
				return new Dictionary<string, string>();
			}
			return Read(new string[1] { text });
		}

		private void WriteAtomic(string path, string content)
		{
			try
			{
				if (!File.Exists(path) || !(File.ReadAllText(path) == content))
				{
					ReplaceThroughTempFile(path, content);
					log.LogInfo((object)(modName + ": wrote " + path));
				}
			}
			catch (Exception ex)
			{
				log.LogError((object)(modName + ": writing " + path + " failed: " + ex.Message));
			}
		}

		private static void ReplaceThroughTempFile(string path, string content)
		{
			string text = path + ".tmp";
			File.WriteAllText(text, content, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
			if (File.Exists(path))
			{
				File.Replace(text, path, null);
			}
			else
			{
				File.Move(text, path);
			}
		}
	}
	internal sealed class YamlFileWatcher
	{
		private const int PollMilliseconds = 5000;

		private readonly YamlFileStore store;

		private readonly IReadOnlyList<YamlFileSet> sets;

		private readonly Action<YamlFileSet> reload;

		private readonly ManualLogSource log;

		private readonly string modName;

		private Timer? timer;

		public YamlFileWatcher(YamlFileStore store, IReadOnlyList<YamlFileSet> sets, Action<YamlFileSet> reload, ManualLogSource log, string modName)
		{
			this.store = store;
			this.sets = sets;
			this.reload = reload;
			this.log = log;
			this.modName = modName;
		}

		public void Start()
		{
			if (timer == null)
			{
				timer = new Timer(delegate
				{
					Poll();
				}, null, 5000, 5000);
			}
		}

		public void TakeSnapshot(YamlFileSet set)
		{
			Dictionary<string, DateTime> dictionary = store.WriteTimes(set);
			lock (set.Snapshot)
			{
				set.Snapshot.Clear();
				foreach (KeyValuePair<string, DateTime> item in dictionary)
				{
					set.Snapshot[item.Key] = item.Value;
				}
			}
		}

		private void Poll()
		{
			try
			{
				foreach (YamlFileSet set in sets.Where(HasChanged))
				{
					if (Interlocked.CompareExchange(ref set.ReloadPending, 1, 0) == 0)
					{
						ThreadingHelper.SynchronizingObject.BeginInvoke((Action)delegate
						{
							Reload(set);
						}, null);
					}
				}
			}
			catch (Exception ex)
			{
				log.LogError((object)(modName + ": watching YAML files failed: " + ex.Message));
			}
		}

		private bool HasChanged(YamlFileSet set)
		{
			Dictionary<string, DateTime> dictionary = store.WriteTimes(set);
			lock (set.Snapshot)
			{
				DateTime value;
				return dictionary.Count != set.Snapshot.Count || dictionary.Any((KeyValuePair<string, DateTime> pair) => !set.Snapshot.TryGetValue(pair.Key, out value) || value != pair.Value);
			}
		}

		private void Reload(YamlFileSet set)
		{
			try
			{
				reload(set);
			}
			catch (Exception arg)
			{
				log.LogError((object)$"{modName}: reloading {set.MainFileName} failed: {arg}");
			}
			finally
			{
				Interlocked.Exchange(ref set.ReloadPending, 0);
			}
		}
	}
	internal static class YamlGameHooks
	{
		private static class Patches
		{
			public static void AfterMenuStart()
			{
				LoadHooked();
			}

			public static void AfterNetworkStart(ZNet __instance)
			{
				if (__instance.IsServer() && __instance.IsDedicated())
				{
					LoadHooked();
				}
			}

			public static void AfterRespawnRequest(Game __instance)
			{
				object obj = firstSpawnField?.GetValue(__instance);
				if (obj is bool && (bool)obj)
				{
					ApplyHooked();
				}
			}
		}

		private static readonly List<YamlFileHub> hooked = new List<YamlFileHub>();

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

		private static bool patched;

		public static void Install(YamlFileHub hub, Harmony harmony, int applyPriority)
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Expected O, but got Unknown
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Expected O, but got Unknown
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Expected O, but got Unknown
			if (!hooked.Contains(hub))
			{
				hooked.Add(hub);
			}
			if (!patched)
			{
				patched = true;
				harmony.Patch((MethodBase)AccessTools.Method(typeof(FejdStartup), "Start", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Patches), "AfterMenuStart", (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(Patches), "AfterNetworkStart", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				HarmonyMethod val = new HarmonyMethod(typeof(Patches), "AfterRespawnRequest", (Type[])null)
				{
					priority = applyPriority
				};
				harmony.Patch((MethodBase)AccessTools.Method(typeof(Game), "RequestRespawn", (Type[])null, (Type[])null), (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
		}

		private static void LoadHooked()
		{
			foreach (YamlFileHub item in hooked)
			{
				item.LoadFromHook();
			}
		}

		private static void ApplyHooked()
		{
			foreach (YamlFileHub item in hooked)
			{
				item.ApplyFromHook();
			}
		}
	}
	internal sealed class YamlGroupTable
	{
		private readonly Dictionary<string, List<string>> members = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);

		private readonly Dictionary<string, List<string>> parents = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);

		public IReadOnlyCollection<string> Names => members.Keys;

		public bool IsGroup(string name)
		{
			return members.ContainsKey(name);
		}

		public void Add(string group, IEnumerable<string> names)
		{
			if (!members.TryGetValue(group, out List<string> value))
			{
				value = new List<string>();
				members.Add(group, value);
			}
			foreach (string name in names)
			{
				value.Add(name);
				if (!parents.TryGetValue(name, out List<string> value2))
				{
					value2 = new List<string>();
					parents.Add(name, value2);
				}
				value2.Add(group);
			}
		}

		public IReadOnlyList<string> Resolve(string name)
		{
			List<string> list = new List<string>();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			Queue<string> queue = new Queue<string>();
			queue.Enqueue(name);
			while (queue.Count > 0)
			{
				if (!parents.TryGetValue(queue.Dequeue(), out List<string> value))
				{
					continue;
				}
				foreach (string item in value)
				{
					if (hashSet.Add(item))
					{
						list.Add(item);
						queue.Enqueue(item);
					}
				}
			}
			return list;
		}

		public IReadOnlyList<string> Expand(string group)
		{
			List<string> result = new List<string>();
			HashSet<string> seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			Expand(group, seen, result);
			return result;
		}

		private void Expand(string name, HashSet<string> seen, List<string> result)
		{
			if (!seen.Add(name))
			{
				return;
			}
			if (!members.TryGetValue(name, out List<string> value))
			{
				result.Add(name);
				return;
			}
			foreach (string item in value)
			{
				Expand(item, seen, result);
			}
		}
	}
	public abstract class YamlModel
	{
		private static readonly IDeserializer Parser = new DeserializerBuilder().Build();

		private string? currentFile;

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

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

		protected YamlGroupTable Groups { get; } = new YamlGroupTable();

		public void Load(string yamlText)
		{
			ReadText(yamlText);
			Verify();
		}

		public void LoadAll(IEnumerable<KeyValuePair<string, string>> files)
		{
			foreach (KeyValuePair<string, string> file in files)
			{
				currentFile = Path.GetFileName(file.Key);
				ReadText(file.Value);
			}
			currentFile = null;
			Verify();
		}

		public IReadOnlyList<string> ResolveGroups(string name)
		{
			return Groups.Resolve(name);
		}

		protected abstract void Read(YamlNode root);

		protected virtual void Verify()
		{
		}

		protected void ReadGroups(YamlNode root, string key = "groups")
		{
			foreach (KeyValuePair<string, YamlNode> entry in root.Get(key).Entries)
			{
				if (entry.Value.TryStringList(out List<string> items))
				{
					Groups.Add(entry.Key, items);
				}
			}
		}

		internal void AddError(string message)
		{
			Errors.Add(WithFile(message));
		}

		internal void AddWarning(string message)
		{
			Warnings.Add(WithFile(message));
		}

		private string WithFile(string message)
		{
			if (currentFile != null)
			{
				return currentFile + ": " + message;
			}
			return message;
		}

		private void ReadText(string yamlText)
		{
			if (TryParse(yamlText, out object graph) && graph != null)
			{
				if (!(graph is IDictionary<object, object>))
				{
					AddError("the file must be a map of keys at the top level, not " + ((graph is IList<object>) ? "a list" : "a single value"));
					return;
				}
				YamlNode yamlNode = new YamlNode(this, graph, "");
				Read(yamlNode);
				yamlNode.WarnUnknownKeys();
			}
		}

		private bool TryParse(string yamlText, out object? graph)
		{
			try
			{
				graph = Parser.Deserialize<object>(yamlText.TrimStart(new char[1] { '\ufeff' }));
				return true;
			}
			catch (Exception ex)
			{
				AddError("invalid YAML: " + ex.Message + ((ex.InnerException == null) ? "" : (" (" + ex.InnerException.Message + ")")));
				graph = null;
				return false;
			}
		}
	}
	internal enum YamlNodeKind
	{
		Scalar,
		List,
		Map,
		Null,
		Missing
	}
	internal sealed class YamlNode
	{
		private readonly YamlModel model;

		private readonly object? value;

		private List<KeyValuePair<string, YamlNode>>? mapEntries;

		private Dictionary<string, YamlNode>? mapLookup;

		private List<YamlNode>? listItems;

		private HashSet<string>? askedKeys;

		private bool visited;

		private bool shapeReported;

		public string Path { get; }

		public YamlNodeKind Kind { get; }

		public string? Text
		{
			get
			{
				if (Kind != YamlNodeKind.Scalar)
				{
					return null;
				}
				return Convert.ToString(value, CultureInfo.InvariantCulture);
			}
		}

		public int Count => Kind switch
		{
			YamlNodeKind.Map => MapEntries.Count, 
			YamlNodeKind.List => ListItems.Count, 
			_ => 0, 
		};

		public IReadOnlyList<KeyValuePair<string, YamlNode>> Entries
		{
			get
			{
				if (!ExpectShape(YamlNodeKind.Map, "a map"))
				{
					return Array.Empty<KeyValuePair<string, YamlNode>>();
				}
				visited = true;
				AskedKeys.UnionWith(MapEntries.Select<KeyValuePair<string, YamlNode>, string>((KeyValuePair<string, YamlNode> e) => e.Key));
				return MapEntries;
			}
		}

		public IReadOnlyList<YamlNode> Items
		{
			get
			{
				if (!ExpectShape(YamlNodeKind.List, "a list"))
				{
					return Array.Empty<YamlNode>();
				}
				return ListItems;
			}
		}

		private HashSet<string> AskedKeys => askedKeys ?? (askedKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase));

		private List<KeyValuePair<string, YamlNode>> MapEntries
		{
			get
			{
				if (mapEntries == null)
				{
					BuildMap();
				}
				return mapEntries;
			}
		}

		private Dictionary<string, YamlNode> MapLookup
		{
			get
			{
				if (mapLookup == null)
				{
					BuildMap();
				}
				return mapLookup;
			}
		}

		private List<YamlNode> ListItems
		{
			get
			{
				if (listItems == null)
				{
					IList<object> source = (IList<object>)value;
					listItems = source.Select((object item, int i) => new YamlNode(model, item, $"{Path}[{i}]")).ToList();
				}
				return listItems;
			}
		}

		internal YamlNode(YamlModel model, object? value, string path, YamlNodeKind kind)
		{
			this.model = model;
			this.value = value;
			Path = path;
			Kind = kind;
		}

		internal YamlNode(YamlModel model, object? value, string path)
			: this(model, value, path, KindOf(value))
		{
		}

		public YamlNode Get(string key)
		{
			string path = (string.IsNullOrEmpty(Path) ? key : (Path + "." + key));
			if (!ExpectShape(YamlNodeKind.Map, "a map"))
			{
				return new YamlNode(model, null, path, YamlNodeKind.Missing);
			}
			visited = true;
			AskedKeys.Add(key);
			if (!MapLookup.TryGetValue(key, out YamlNode result))
			{
				return new YamlNode(model, null, path, YamlNodeKind.Missing);
			}
			return result;
		}

		public bool Is(string keyword)
		{
			if (Kind == YamlNodeKind.Scalar)
			{
				return string.Equals(Text.Trim(), keyword, StringComparison.OrdinalIgnoreCase);
			}
			return false;
		}

		public void Error(string message)
		{
			model.AddError(Prefixed(message));
		}

		public void Warn(string message)
		{
			model.AddWarning(Prefixed(message));
		}

		public void WarnUnknownKeys()
		{
			if (Kind == YamlNodeKind.Map && visited)
			{
				List<string> list = (from e in MapEntries
					select e.Key into k
					where !AskedKeys.Contains(k)
					select k).ToList();
				if (list.Count > 0)
				{
					Warn("unknown keys " + string.Join(", ", list));
				}
				{
					foreach (KeyValuePair<string, YamlNode> mapEntry in MapEntries)
					{
						mapEntry.Value.WarnUnknownKeys();
					}
					return;
				}
			}
			if (Kind == YamlNodeKind.List && listItems != null)
			{
				listItems.ForEach(delegate(YamlNode item)
				{
					item.WarnUnknownKeys();
				});
			}
		}

		private string Prefixed(string message)
		{
			if (!string.IsNullOrEmpty(Path))
			{
				return Path + ": " + message;
			}
			return message;
		}

		private void BuildMap()
		{
			mapEntries = new List<KeyValuePair<string, YamlNode>>();
			mapLookup = new Dictionary<string, YamlNode>(StringComparer.OrdinalIgnoreCase);
			foreach (KeyValuePair<object, object> item in (IDictionary<object, object>)value)
			{
				string text = Convert.ToString(item.Key, CultureInfo.InvariantCulture) ?? "";
				string path = (string.IsNullOrEmpty(Path) ? text : (Path + "." + text));
				YamlNode yamlNode = new YamlNode(model, item.Value, path);
				if (mapLookup.ContainsKey(text))
				{
					yamlNode.Warn("duplicate key (differs only in case), ignored");
					continue;
				}
				mapLookup.Add(text, yamlNode);
				mapEntries.Add(new KeyValuePair<string, YamlNode>(text, yamlNode));
			}
		}

		private bool ExpectShape(YamlNodeKind wanted, string description)
		{
			if (Kind == wanted)
			{
				return true;
			}
			if (Kind != YamlNodeKind.Missing && !shapeReported)
			{
				shapeReported = true;
				Error((Kind == YamlNodeKind.Null) ? ("expected " + description + " but the key has no value") : ("expected " + description + ", found " + Describe(Kind)));
			}
			return false;
		}

		private static string Describe(YamlNodeKind kind)
		{
			return kind switch
			{
				YamlNodeKind.Scalar => "a single value", 
				YamlNodeKind.List => "a list", 
				YamlNodeKind.Map => "a map", 
				_ => "nothing", 
			};
		}

		private static YamlNodeKind KindOf(object? value)
		{
			if (value != null)
			{
				if (!(value is IDictionary<object, object>))
				{
					if (value is IList<object>)
					{
						return YamlNodeKind.List;
					}
					return YamlNodeKind.Scalar;
				}
				return YamlNodeKind.Map;
			}
			return YamlNodeKind.Null;
		}

		public bool TryInt(out int result)
		{
			return TryScalar<int>(out result);
		}

		public bool TryFloat(out float result)
		{
			return TryScalar<float>(out result);
		}

		public bool TryBool(out bool result)
		{
			return TryScalar<bool>(out result);
		}

		public bool TryString(out string result)
		{
			return TryScalar<string>(out result);
		}

		public bool TryEnum<T>(out T result) where T : struct, Enum
		{
			return TryScalar<T>(out result);
		}

		public bool TryList<T>(out List<T> items)
		{
			items = new List<T>();
			if (!ExpectShape(YamlNodeKind.List, "a list of " + YamlScalarParser.Plural<T>()))
			{
				return false;
			}
			bool result = true;
			foreach (YamlNode listItem in ListItems)
			{
				if (listItem.TryScalar<T>(out var result2))
				{
					items.Add(result2);
				}
				else
				{
					result = false;
				}
			}
			return result;
		}

		public bool TryStringList(out List<string> items)
		{
			if (Kind == YamlNodeKind.Scalar)
			{
				items = new List<string> { Text };
				return true;
			}
			return TryList(out items);
		}

		private bool TryScalar<T>(out T result)
		{
			result = default(T);
			if (!ExpectShape(YamlNodeKind.Scalar, YamlScalarParser.Describe(typeof(T))))
			{
				return false;
			}
			if (YamlScalarParser.TryParse(Text, typeof(T), out object result2))
			{
				result = (T)result2;
				return true;
			}
			Error("'" + Text + "' is not " + YamlScalarParser.Describe(typeof(T)));
			return false;
		}
	}
	internal static class YamlScalarParser
	{
		private static readonly string[] TrueWords = new string[4] { "true", "yes", "on", "1" };

		private static readonly string[] FalseWords = new string[4] { "false", "no", "off", "0" };

		public static bool TryParse(string text, Type type, out object? result)
		{
			string text2 = text.Trim();
			result = null;
			if (type == typeof(string))
			{
				result = text;
				return true;
			}
			if (type == typeof(int))
			{
				int result3;
				bool result2 = int.TryParse(text2, NumberStyles.Integer, CultureInfo.InvariantCulture, out result3);
				result = result3;
				return result2;
			}
			if (type == typeof(float))
			{
				float result5;
				bool result4 = float.TryParse(text2, NumberStyles.Float, CultureInfo.InvariantCulture, out result5);
				result = result5;
				return result4;
			}
			if (!(type == typeof(bool)))
			{
				return TryParseEnum(text2, type, out result);
			}
			return TryParseBool(text2, out result);
		}

		public static string Describe(Type type)
		{
			if (type == typeof(int))
			{
				return "an integer";
			}
			if (type == typeof(float))
			{
				return "a number";
			}
			if (type == typeof(bool))
			{
				return "true or false";
			}
			if (!type.IsEnum)
			{
				return "text";
			}
			return "one of " + string.Join(", ", Enum.GetNames(type));
		}

		public static string Plural<T>()
		{
			if (!(typeof(T) == typeof(int)))
			{
				if (!(typeof(T) == typeof(float)))
				{
					if (!(typeof(T) == typeof(bool)))
					{
						if (!typeof(T).IsEnum)
						{
							return "text values";
						}
						return "names (" + string.Join(", ", Enum.GetNames(typeof(T))) + ")";
					}
					return "true/false values";
				}
				return "numbers";
			}
			return "integers";
		}

		private static bool TryParseBool(string text, out object? result)
		{
			if (TrueWords.Contains<string>(text, StringComparer.OrdinalIgnoreCase))
			{
				result = true;
				return true;
			}
			result = false;
			return FalseWords.Contains<string>(text, StringComparer.OrdinalIgnoreCase);
		}

		private static bool TryParseEnum(string text, Type type, out object? result)
		{
			if (!type.IsEnum)
			{
				throw new NotSupportedException("YamlNode cannot read values of type " + type.Name);
			}
			string text2 = Enum.GetNames(type).FirstOrDefault((string n) => string.Equals(n, text, StringComparison.OrdinalIgnoreCase));
			result = ((text2 == null) ? null : Enum.Parse(type, text2));
			return text2 != null;
		}
	}
}
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
				{
					if (SnapshotChanged())
					{
						string text = ReadOrNull(config.ConfigFilePath);
						if (text != null && !(text == lastContent))
						{
							lastContent = text;
							ScheduleCheck();
						}
					}
				}
				catch (Exception ex)
				{
					ManualLogSource? obj = log;
					if (obj != null)
					{
						obj.LogError((object)("Watching " + fileName + " failed: " + ex.Message));
					}
				}
			}

			private bool SnapshotChanged()
			{
				Snapshot(out var writeTime, out var length);
				if (writeTime == lastWriteTime && length == lastLength)
				{
					return false;
				}
				lastWriteTime = writeTime;
				lastLength = length;
				return true;
			}

			private void ScheduleCheck()
			{
				if (Interlocked.CompareExchange(ref reloading, 1, 0) == 0)
				{
					ThreadingHelper.SynchronizingObject.BeginInvoke(new Action(CheckAndReload), null);
				}
			}

			private void CheckAndReload()
			{
				try
				{
					if (!LoadedValues.Matches(config, lastContent))
					{
						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
				{
					Resnapshot();
					Interlocked.Exchange(ref reloading, 0);
				}
			}

			private void Resnapshot()
			{
				Snapshot(out lastWriteTime, out lastLength);
				lastContent = ReadOrNull(config.ConfigFilePath) ?? lastContent;
			}

			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);
		}
	}
	internal static class LoadedValues
	{
		private static readonly PropertyInfo? orphans = typeof(ConfigFile).GetProperty("OrphanedEntries", BindingFlags.Instance | BindingFlags.NonPublic);

		public static bool Matches(ConfigFile config, string content)
		{
			Dictionary<ConfigDefinition, string> dictionary = Parse(content);
			Dictionary<ConfigDefinition, string> dictionary2 = Loaded(config);
			if (dictionary2 == null)
			{
				return false;
			}
			if (dictionary.Count != dictionary2.Count)
			{
				return false;
			}
			foreach (KeyValuePair<ConfigDefinition, string> item in dictionary)
			{
				if (!dictionary2.TryGetValue(item.Key, out var value) || !string.Equals(value, item.Value, StringComparison.Ordinal))
				{
					return false;
				}
			}
			return true;
		}

		private static Dictionary<ConfigDefinition, string>? Loaded(ConfigFile config)
		{
			if (!(orphans?.GetValue(config) is Dictionary<ConfigDefinition, string> dictionary))
			{
				return null;
			}
			Dictionary<ConfigDefinition, string> dictionary2 = new Dictionary<ConfigDefinition, string>(dictionary);
			foreach (KeyValuePair<ConfigDefinition, ConfigEntryBase> item in config)
			{
				dictionary2[item.Key] = item.Value.GetSerializedValue();
			}
			return dictionary2;
		}

		private static Dictionary<ConfigDefinition, string> Parse(string content)
		{
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Expected O, but got Unknown
			Dictionary<ConfigDefinition, string> dictionary = new Dictionary<ConfigDefinition, string>();
			string text = "";
			string[] array = content.Split(new char[1] { '\n' });
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim();
				if (text2.Length == 0 || text2.StartsWith("#", StringComparison.Ordinal))
				{
					continue;
				}
				if (text2.StartsWith("[", StringComparison.Ordinal) && text2.EndsWith("]", StringComparison.Ordinal))
				{
					text = text2.Substring(1, text2.Length - 2);
					continue;
				}
				int num = text2.IndexOf('=');
				if (num > 0)
				{
					dictionary[new ConfigDefinition(text, text2.Substring(0, num).Trim())] = text2.Substring(num + 1).Trim();
				}
			}
			return dictionary;
		}
	}
}
namespace Charter
{
	internal sealed class Amendments
	{
		private readonly Charter charter;

		private readonly Ledger ledger;

		private readonly Journal journal;

		private readonly string rpcName;

		public Amendments(Charter charter, Ledger ledger, Journal journal)
		{
			this.charter = charter;
			this.ledger = ledger;
			this.journal = journal;
			rpcName = "Charter_" + charter.Guid + "_Amend";
		}

		public void Send(IClause clause)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Expected O, but got Unknown
			ZRpc rpc = charter.Receiver.Rpc;
			if (rpc != null && rpc.IsConnected())
			{
				string currentToml = clause.CurrentToml;
				ZPackage val = new ZPackage();
				val.Write(1);
				val.Write(charter.Guid);
				val.Write(clause.Section);
				val.Write(clause.Key);
				val.Write(currentToml);
				rpc.Invoke(rpcName, new object[1] { val });
				journal.Info("amendment sent: " + clause.Section + "." + clause.Key + " = " + currentToml);
			}
		}

		public void OnAmend(ZRpc rpc, ZPackage pkg)
		{
			try
			{
				Receive(rpc, pkg);
			}
			catch (Exception arg)
			{
				journal.Error($"handling an amendment failed: {arg}");
			}
		}

		private void Receive(ZRpc rpc, ZPackage pkg)
		{
			if (pkg.ReadInt() != 1 || pkg.ReadString() != charter.Guid)
			{
				journal.Warning("amendment with a foreign protocol or guid dropped");
				return;
			}
			string section = pkg.ReadString();
			string key = pkg.ReadString();
			string toml = pkg.ReadString();
			ZNetPeer val = Side.PeerOf(rpc);
			if (val != null && Side.IsServer)
			{
				string text = Accept(val, section, key, toml);
				if (text != null)
				{
					Reject(val, section, key, text);
				}
			}
		}

		private string? Accept(ZNetPeer peer, string section, string key, string toml)
		{
			if (!Stewardship.IsSteward(peer))
			{
				return "you are not a steward";
			}
			IClause clause = ledger.Find(section, key);
			if (clause == null)
			{
				return "unknown clause";
			}
			if (clause.Local)
			{
				return "the clause is local";
			}
			try
			{
				clause.Entry.SetSerializedValue(toml);
			}
			catch (Exception ex)
			{
				return "bad value: " + ex.Message;
			}
			clause.Entry.ConfigFile.Save();
			journal.Info("amendment by " + Side.NameOf(peer) + " accepted: " + section + "." + key + " = " + toml);
			return null;
		}

		private void Reject(ZNetPeer peer, string section, string key, string reason)
		{
			journal.Warning("amendment by " + Side.NameOf(peer) + " rejected: " + section + "." + key + ": " + reason);
			charter.Publisher.SendNotice(peer, ledger.Find(section, key), "amendment of " + section + "." + key + " rejected: " + reason);
		}
	}
	internal sealed class Article<T> : IArticle
	{
		private readonly Charter charter;

		private readonly ArticleKind kind;

		private T value;

		public string Name { get; }

		public bool Standing { get; }

		public T Value => value;

		ArticleKind IArticle.Kind => kind;

		object IArticle.Boxed => value;

		public event Action? Changed;

		public Article(Charter charter, string name, T initial, bool standing = false)
		{
			kind = ArticleValues.KindOf(typeof(T));
			this.charter = charter;
			Name = name;
			Standing = standing;
			value = initial;
			charter.Ledger.Add(this);
		}

		public void Assign(T value)
		{
			if (!charter.IsAuthor)
			{
				charter.Journal.Warning("article '" + Name + "' cannot be assigned while the server's charter binds");
			}
			else if (!ArticleValues.Same(this.value, value))
			{
				this.value = value;
				Raise();
				charter.Publisher.MarkDirty(this);
			}
		}

		bool IArticle.Accept(object incoming)
		{
			if (ArticleValues.Same(value, incoming))
			{
				return false;
			}
			value = (T)incoming;
			return true;
		}

		void IArticle.Raise()
		{
			Raise();
		}

		private void Raise()
		{
			try
			{
				this.Changed?.Invoke();
			}
			catch (Exception arg)
			{
				charter.Journal.Error($"a Changed handler of article '{Name}' threw: {arg}");
			}
		}
	}
	internal interface IArticle
	{
		string Name { get; }

		bool Standing { get; }

		ArticleKind Kind { get; }

		object Boxed { get; }

		bool Accept(object incoming);

		void Raise();
	}
	internal enum ArticleKind : byte
	{
		Text = 1,
		TextList,
		Integer,
		Number,
		Toggle
	}
	internal static class ArticleValues
	{
		public static ArticleKind KindOf(Type type)
		{
			if (type == typeof(string))
			{
				return ArticleKind.Text;
			}
			if (type == typeof(List<string>))
			{
				return ArticleKind.TextList;
			}
			if (type == typeof(int))
			{
				return ArticleKind.Integer;
			}
			if (type == typeof(float))
			{
				return ArticleKind.Number;
			}
			if (type == typeof(bool))
			{
				return ArticleKind.Toggle;
			}
			throw new ArgumentException("Charter articles hold string, List<string>, int, float or bool, not " + type.Name);
		}

		public static void Write(ZPackage pkg, ArticleKind kind, object? value)
		{
			switch (kind)
			{
			case ArticleKind.Text:
				pkg.Write((value as string) ?? "");
				break;
			case ArticleKind.TextList:
				WriteList(pkg, value as List<string>);
				break;
			case ArticleKind.Integer:
				pkg.Write((value is int num2) ? num2 : 0);
				break;
			case ArticleKind.Number:
				pkg.Write((value is float num3) ? num3 : 0f);
				break;
			case ArticleKind.Toggle:
			{
				bool flag = default(bool);
				int num;
				if (value is bool)
				{
					flag = (bool)value;
					num = 1;
				}
				else
				{
					num = 0;
				}
				pkg.Write((byte)((uint)num & (flag ? 1u : 0u)) != 0);
				break;
			}
			default:
				throw new ArgumentException($"unknown article kind {kind}");
			}
		}

		public static object Read(ZPackage pkg, ArticleKind kind)
		{
			return kind switch
			{
				ArticleKind.Text => pkg.ReadString(), 
				ArticleKind.TextList => ReadList(pkg), 
				ArticleKind.Integer => pkg.ReadInt(), 
				ArticleKind.Number => pkg.ReadSingle(), 
				ArticleKind.Toggle => pkg.ReadBool(), 
				_ => throw new ArgumentException($"unknown article kind {kind}"), 
			};
		}

		public static int Size(ArticleKind kind, object? value)
		{
			return kind switch
			{
				ArticleKind.Text => (value as string)?.Length ?? 0, 
				ArticleKind.TextList => (value as List<string>)?.Sum((string s) => s?.Length ?? 0) ?? 0, 
				_ => 4, 
			};
		}

		public static bool Same(object? a, object? b)
		{
			if (a is List<string> first)
			{
				List<string> second = (b as List<string>) ?? new List<string>();
				return first.SequenceEqual<string>(second, StringComparer.Ordinal);
			}
			if (b is List<string> list)
			{
				if (list.Count == 0)
				{
					return a == null;
				}
				return false;
			}
			return object.Equals(a, b);
		}

		private static void WriteList(ZPackage pkg, List<string>? list)
		{
			pkg.Write(list?.Count ?? 0);
			foreach (string item in list ?? new List<string>())
			{
				pkg.Write(item ?? "");
			}
		}

		private static List<string> ReadList(ZPackage pkg)
		{
			int num = pkg.ReadInt();
			List<string> list = new List<string>(Math.Max(0, num));
			for (int i = 0; i < num; i++)
			{
				list.Add(pkg.ReadString());
			}
			return list;
		}
	}
	internal sealed class ChangeRouter
	{
		private readonly Charter charter;

		private readonly Ledger ledger;

		private readonly Journal journal;

		private readonly HashSet<ConfigFile> watched = new HashSet<ConfigFile>();

		public ChangeRouter(Charter charter, Ledger ledger, Journal journal)
		{
			this.charter = charter;
			this.ledger = ledger;
			this.journal = journal;
		}

		public void Watch(ConfigFile file)
		{
			if (watched.Add(file))
			{
				file.SettingChanged += OnSettingChanged;
			}
		}

		private void OnSettingChanged(object sender, SettingChangedEventArgs args)
		{
			try
			{
				if (!ledger.Applying)
				{
					Route(args.ChangedSetting);
				}
			}
			catch (Exception arg)
			{
				journal.Error($"handling a change of {args.ChangedSetting.Definition} failed: {arg}");
			}
		}

		private void Route(ConfigEntryBase entry)
		{
			IClause clause = ledger.Find(entry);
			if (clause == null)
			{
				return;
			}
			clause.RememberOwn();
			if (Side.IsServer)
			{
				charter.Publisher.MarkDirty(clause);
				return;
			}
			Receiver receiver = charter.Receiver;
			if (receiver.Bound && !clause.Local)
			{
				if (receiver.Steward)
				{
					charter.Amendments.Send(clause);
					return;
				}
				journal.Info(clause.Section + "." + clause.Key + " is bound by the server, reverting to " + clause.AuthorToml);
				clause.ApplyAuthor();
			}
		}
	}
	public sealed class Charter
	{
		private bool lastBound;

		private bool lastSteward;

		public string Guid { get; }

		public string Title { get; }

		public string Version { get; }

		public string OldestAccepted { get; }

		public bool Mandatory { get; }

		public bool IsBound
		{
			get
			{
				if (!Side.IsServer)
				{
					return Receiver.Bound;
				}
				return Ledger.BindingOn;
			}
		}

		public bool IsAuthor
		{
			get
			{
				if (!Side.IsServer)
				{
					return !Receiver.Bound;
				}
				return true;
			}
		}

		public bool MayAmend
		{
			get
			{
				if (IsBound)
				{
					return IsSteward;
				}
				return true;
			}
		}

		public bool IsSteward
		{
			get
			{
				if (!Side.IsServer)
				{
					return Receiver.Steward;
				}
				return true;
			}
		}

		public DateTime? LastPush
		{
			get
			{
				if (!Side.IsServer)
				{
					return Receiver.LastPush;
				}
				return null;
			}
		}

		public int PushedBytes => Receiver.PushedBytes;

		public static Verbosity Verbosity { get; set; } = Verbosity.Normal;

		internal Journal Journal { get; }

		internal Ledger Ledger { get; }

		internal Receiver Receiver { get; }

		internal Courier Courier { get; }

		internal Publisher Publisher { get; }

		internal Stewardship Stewardship { get; }

		internal Amendments Amendments { get; }

		internal ChangeRouter Router { get; }

		internal ReadOnlyTags Tags { get; }

		public event Action<bool>? Pushed;

		public event Action? StewardChanged;

		public event Action? BindingChanged;

		public Charter(string guid, string title, string version, string? oldestAccepted = null, bool mandatory = true)
		{
			Guid = guid;
			Title = title;
			Version = version;
			OldestAccepted = oldestAccepted ?? version;
			Mandatory = mandatory;
			Journal = new Journal(title);
			Ledger = new Ledger();
			Receiver = new Receiver(this, Ledger, Journal);
			Courier = new Courier(guid, Journal);
			Publisher = new Publisher(Ledger, Courier, Journal);
			Stewardship = new Stewardship(Courier, Publisher);
			Amendments = new Amendments(this, Ledger, Journal);
			Router = new ChangeRouter(this, Ledger, Journal);
			Tags = new ReadOnlyTags();
			lastSteward = IsSteward;
			Enrol();
		}

		public Clause<T> Clause<T>(ConfigEntry<T> entry, bool local = false)
		{
			Clause<T> clause = new Clause<T>(entry, local, Ledger);
			Ledger.Add(clause);
			Router.Watch(((ConfigEntryBase)entry).ConfigFile);
			return clause;
		}

		public void Binding(ConfigEntry<bool> entry)
		{
			Clause<bool> clause = Clause<bool>(entry);
			Ledger.SetBinding(clause, () => entry.Value);
		}

		public static void Install(Harmony harmony)
		{
			GameHooks.Install(harmony);
		}

		internal void Attach(ZNetPeer peer, bool server)
		{
			if (server)
			{
				peer.m_rpc.Register<ZPackage>("Charter_" + Guid + "_Amend", (Action<ZRpc, ZPackage>)Amendments.OnAmend);
			}
			else
			{
				peer.m_rpc.Register<ZPackage>("Charter_" + Guid + "_Push", (Action<ZRpc, ZPackage>)Receiver.OnPush);
			}
		}

		internal void Tick(float now)
		{
			Publisher.Flush();
			Courier.Tick();
			Stewardship.Tick(now);
			Receiver.Poll();
			NoteChanges();
		}

		internal void AfterPush(bool first)
		{
			NoteChanges();
			try
			{
				this.Pushed?.Invoke(first);
			}
			catch (Exception arg)
			{
				Journal.Error($"a Pushed handler threw: {arg}");
			}
		}

		internal void AfterSessionEnd()
		{
			NoteChanges();
		}

		private void Enrol()
		{
			GameHooks.Track(this);
			Family.Register(new FamilyEntry(Guid, Title, Version, OldestAccepted, Mandatory));
			Family.SetStatus(Guid, () => Reports.Status(this));
			Family.SetDiff(Guid, () => Reports.Diff(this));
			Family.SetVerbosity(Guid, delegate(int level)
			{
				Verbosity = (Verbosity)level;
			});
		}

		private void NoteChanges()
		{
			bool isBound = IsBound;
			bool isSteward = IsSteward;
			Tags.Update(isBound && !isSteward, Ledger.Pushable);
			if (isBound != lastBound)
			{
				lastBound = isBound;
				Raise(this.BindingChanged, "BindingChanged");
			}
			if (isSteward != lastSteward)
			{
				lastSteward = isSteward;
				Raise(this.StewardChanged, "StewardChanged");
			}
		}

		private void Raise(Action? handler, string name)
		{
			try
			{
				handler?.Invoke();
			}
			catch (Exception arg)
			{
				Journal.Error($"a {name} handler threw: {arg}");
			}
		}
	}
	public sealed class Clause<T> : IClause
	{
		private readonly Ledger ledger;

		private T own;

		private T author;

		public ConfigEntry<T> Entry { get; }

		public bool Local { get; }

		public T AuthorValue
		{
			get
			{
				if (!Side.IsServer)
				{
					return author;
				}
				return Entry.Value;
			}
		}

		public T OwnValue => own;

		ConfigEntryBase IClause.Entry => (ConfigEntryBase)(object)Entry;

		string IClause.Section => ((ConfigEntryBase)Entry).Definition.Section;

		string IClause.Key => ((ConfigEntryBase)Entry).Definition.Key;

		public string AuthorToml { get; private set; }

		string IClause.OwnToml => TomlTypeConverter.ConvertToString((object)own, typeof(T));

		string IClause.CurrentToml => ((ConfigEntryBase)Entry).GetSerializedValue();

		internal Clause(ConfigEntry<T> entry, bool local, Ledger ledger)
		{
			Entry = entry;
			Local = local;
			this.ledger = ledger;
			own = entry.Value;
			author = default(T);
			AuthorToml = "";
		}

		void IClause.RememberOwn()
		{
			own = Entry.Value;
		}

		bool IClause.TakeAuthor(string toml)
		{
			try
			{
				author = TomlTypeConverter.ConvertToValue<T>(toml);
				AuthorToml = toml;
				return true;
			}
			catch (Exception)
			{
				return false;
			}
		}

		void IClause.ApplyAuthor()
		{
			ledger.Quietly((ConfigEntryBase)(object)Entry, delegate
			{
				Entry.Value = author;
			});
		}

		void IClause.RestoreOwn()
		{
			ledger.Quietly((ConfigEntryBase)(obj