Decompiled source of NoVikingLeftBehind v0.11.3

NoVikingLeftBehind.dll

Decompiled a month ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
using ServerSync;
using Splatform;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using Valheim.SettingsGui;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: IgnoresAccessChecksTo("assembly_guiutils")]
[assembly: IgnoresAccessChecksTo("assembly_utils")]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: IgnoresAccessChecksTo("com.rlabrecque.steamworks.net")]
[assembly: AssemblyCompany("NoVikingLeftBehind")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.11.3.0")]
[assembly: AssemblyInformationalVersion("0.11.3+0dac9dc7e9326f5cefdbb9574a4abef99ec30fea")]
[assembly: AssemblyProduct("NoVikingLeftBehind")]
[assembly: AssemblyTitle("NoVikingLeftBehind")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.11.3.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 ServerSync
{
	[PublicAPI]
	public abstract class OwnConfigEntryBase
	{
		public object? LocalBaseValue;

		public bool SynchronizedConfig = true;

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

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

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

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

		public readonly string Identifier;

		public readonly Type Type;

		private object? boxedValue;

		protected bool localIsOwner;

		public readonly int Priority;

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

		public event Action? ValueChanged;

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

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

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

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

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

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

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

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

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

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

				public volatile int versionMatchQueued = -1;

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

				public readonly ISocket Original;

				public BufferingSocket(ISocket original)
				{
					Original = original;
					((ZPlayFabSocket)this)..ctor();
				}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		private class PackageEntry
		{
			public string section;

			public string key;

			public Type type;

			public object? value;
		}

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

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

		private class InvalidDeserializationTypeException : Exception
		{
			public string expected;

			public string received;

			public string field = "";
		}

		public static bool ProcessingServerUpdate;

		public readonly string Name;

		public string? DisplayName;

		public string? CurrentVersion;

		public string? MinimumRequiredVersion;

		public bool ModRequired;

		private bool? forceConfigLocking;

		private bool isSourceOfTruth = true;

		private static readonly HashSet<ConfigSync> configSyncs;

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

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

		private static bool isServer;

		private static bool lockExempt;

		private OwnConfigEntryBase? lockedConfig;

		private const byte PARTIAL_CONFIGS = 1;

		private const byte FRAGMENTED_CONFIG = 2;

		private const byte COMPRESSED_CONFIG = 4;

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

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

		private static long packageCounter;

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

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

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

		public bool InitialSyncDone { get; private set; }

		public event Action<bool>? SourceOfTruthChanged;

		private event Action? lockedConfigChanged;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		private static readonly Dictionary<string, string> notProcessedNames;

		public string Name;

		private string? displayName;

		private string? currentVersion;

		private string? minimumRequiredVersion;

		public bool ModRequired = true;

		private string? ReceivedCurrentVersion;

		private string? ReceivedMinimumRequiredVersion;

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

		private ConfigSync? ConfigSync;

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

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

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

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

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

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

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

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

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

		private string ErrorClient()
		{
			if (ReceivedMinimumRequiredVersion == null)
			{
				return DisplayName + " is not installed on the server.";
			}
			if (!(new Version(CurrentVersion) >= new Version(ReceivedMinimumRequiredVersion)))
			{
				return DisplayName + " needs to be at least version " + ReceivedMinimumRequiredVersion + ". You have version " + CurrentVersion + ".";
			}
			return DisplayName + " may not be higher than version " + ReceivedCurrentVersion + ". You have version " + CurrentVersion + ".";
		}

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

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

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

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

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

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

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

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

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

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

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

		[HarmonyPatch(typeof(FejdStartup), "ShowConnectError")]
		[HarmonyPostfix]
		private static void ShowConnectionError(FejdStartup __instance)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Invalid comparison between Unknown and I4
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0153: Unknown result type (might be due to invalid IL or missing references)
			//IL_0161: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01be: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01de: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ee: Unknown result type (might be due to invalid IL or missing references)
			if (!__instance.m_connectionFailedPanel.activeSelf || (int)ZNet.GetConnectionStatus() != 3)
			{
				return;
			}
			bool flag = false;
			VersionCheck[] failedClient = GetFailedClient();
			if (failedClient.Length != 0)
			{
				string text = string.Join("\n", failedClient.Select((VersionCheck check) => check.Error()));
				TMP_Text connectionFailedError = __instance.m_connectionFailedError;
				connectionFailedError.text = connectionFailedError.text + "\n" + text;
				flag = true;
			}
			foreach (KeyValuePair<string, string> item in notProcessedNames.OrderBy<KeyValuePair<string, string>, string>((KeyValuePair<string, string> kv) => kv.Key))
			{
				if (!__instance.m_connectionFailedError.text.Contains(item.Key))
				{
					TMP_Text connectionFailedError2 = __instance.m_connectionFailedError;
					connectionFailedError2.text = connectionFailedError2.text + "\nServer expects you to have " + item.Key + " (Version: " + item.Value + ") installed.";
					flag = true;
				}
			}
			if (flag)
			{
				RectTransform component = ((Component)__instance.m_connectionFailedPanel.transform.Find("Image")).GetComponent<RectTransform>();
				Vector2 sizeDelta = component.sizeDelta;
				sizeDelta.x = 675f;
				component.sizeDelta = sizeDelta;
				__instance.m_connectionFailedError.ForceMeshUpdate(false, false);
				float num = __instance.m_connectionFailedError.renderedHeight + 105f;
				RectTransform component2 = ((Component)((Component)component).transform.Find("ButtonOk")).GetComponent<RectTransform>();
				component2.anchoredPosition = new Vector2(component2.anchoredPosition.x, component2.anchoredPosition.y - (num - component.sizeDelta.y) / 2f);
				sizeDelta = component.sizeDelta;
				sizeDelta.y = num;
				component.sizeDelta = sizeDelta;
			}
		}
	}
}
namespace NoVikingLeftBehind
{
	public enum TweakAccess
	{
		Everyone,
		Admins
	}
	public enum Announce
	{
		Chat,
		Message,
		Both,
		Off
	}
	internal sealed class AccessModule : FeatureModule
	{
		private static AccessModule _inst;

		private ConfigEntry<TweakAccess> _who;

		private ConfigEntry<Announce> _announce;

		private ConfigEntry<int> _rate;

		private ConfigEntry<bool> _selfTest;

		private ConfigEntry<bool> _netSelfTest;

		public override string Name => "Access";

		public override string Section => "Access";

		public override ModuleSide Side => ModuleSide.Both;

		public override string Theme => "Server";

		public override string Hint => "Who may change settings from the in-game menu";

		protected override string EnabledDescription => "Enable the in-game settings door: the RPC that lets players change settings from Valheim's own Settings menu. Off = the tab still lists everything, but nothing can be changed from it and only the cfg file (or an admin's ConfigurationManager) works.";

		protected override Opt EnabledOpt => base.EnabledOpt.Admin();

		public static bool DoorOpen
		{
			get
			{
				if (_inst != null)
				{
					return _inst.Active;
				}
				return false;
			}
		}

		public static bool AdminsOnly
		{
			get
			{
				if (_inst != null && _inst._who != null)
				{
					return _inst._who.Value == TweakAccess.Admins;
				}
				return false;
			}
		}

		public static Announce AnnounceMode
		{
			get
			{
				if (_inst == null || _inst._announce == null)
				{
					return Announce.Chat;
				}
				return _inst._announce.Value;
			}
		}

		public static int RateLimit
		{
			get
			{
				if (_inst == null || _inst._rate == null)
				{
					return 10;
				}
				return Math.Max(1, _inst._rate.Value);
			}
		}

		public static bool SelfTestWanted
		{
			get
			{
				if (_inst != null && _inst._selfTest != null)
				{
					return _inst._selfTest.Value;
				}
				return false;
			}
		}

		private AccessModule()
		{
			_inst = this;
		}

		public static string WhoMayTweakText()
		{
			if (!DoorOpen)
			{
				return "Changing settings from this menu is switched off on this server.";
			}
			if (!AdminsOnly)
			{
				return "Everyone on this server can change these settings.";
			}
			return "Only server admins can change settings here.";
		}

		protected override void Bind()
		{
			_inst = this;
			_who = BindSynced("TweakAccess", TweakAccess.Everyone, "Who may change a server-synced setting from the in-game settings tab. Everyone = any player on the server (settings marked admin-only still are). Admins = only players in adminlist.txt. Changing this takes effect immediately.", Opt.C("Everyone = any player here; Admins = only adminlist.txt").Admin().Simple("Server & safety", 10)
				.As("Who may change these settings"));
			_announce = BindSynced("Announce", Announce.Chat, "How a change is told to the rest of the server. Chat = one chat line attributed to the player who made it. Message = a top-left message on everyone's screen. Both = both. Off = nothing on screen (the server log still records every change).", Opt.C("How changes are announced to everyone").Admin());
			_rate = BindSynced("MaxChangesPer10s", 10, "How many changes one player may make in any ten seconds before the server starts refusing. Stops an accident (or a joke) from rewriting the whole config at once.", Opt.N("How many changes one player may make per ten seconds", 1.0, 60.0, 1.0).Admin());
			_selfTest = BindSynced("SelfTest", defaultValue: false, "Dedicated server only: at world load, drive the whole settings door once - change a harmless setting through the same path a player's menu uses, prove the cfg file on disk changed (with its timestamped backup), hold it for 45 seconds so it can be seen from outside, then undo it. Everything is written to the log. Off by default.", Opt.B("Prove the settings door works, at server start").Admin().Diag());
			_netSelfTest = BindSynced("NetworkSelfTest", defaultValue: false, "Dedicated server only: at world load, drive the settings door against the SmoothServer settings the Network panel exposes - flip the network preset and back, re-reading the cfg file from disk each time, undo both changes, and prove the door refuses a SmoothServer key that is not on the panel's allowlist and an admin-only one asked for by a non-admin. Everything is restored. Needs SmoothServer installed on this server; otherwise it logs that and stops. Off by default.", Opt.B("Prove the Network panel's door works, at server start").Admin().Diag());
		}

		public static void Pump()
		{
			if (_inst != null && _inst.Active)
			{
				if (_inst._selfTest != null && _inst._selfTest.Value)
				{
					AccessSelfTest.Pump();
				}
				if (_inst._netSelfTest != null && _inst._netSelfTest.Value)
				{
					NetworkSelfTest.Pump();
				}
			}
		}

		protected override void ApplyPatches()
		{
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method(typeof(Game), "Start", (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				throw new Exception("Game.Start() not found - cannot register the settings RPCs");
			}
			if (AccessTools.Method(typeof(ZNet), "IsAdmin", new Type[1] { typeof(string) }, (Type[])null) == null)
			{
				throw new Exception("ZNet.IsAdmin(string) not found");
			}
			if (AccessTools.Method(typeof(ZNet), "GetPlayerList", (Type[])null, (Type[])null) == null)
			{
				throw new Exception("ZNet.GetPlayerList() not found");
			}
			Harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(AccessModule), "GameStartPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}

		private static void GameStartPostfix()
		{
			try
			{
				TweakDoor.RegisterRpcs();
			}
			catch (Exception ex)
			{
				FeatureModule.Log.LogError((object)("[Access] RPC registration failed: " + ex));
			}
			try
			{
				if (_inst != null && _inst.Active && FeatureModule.ServerActive() && _inst._selfTest != null && _inst._selfTest.Value)
				{
					AccessSelfTest.Arm();
				}
				if (_inst != null && _inst.Active && FeatureModule.ServerActive() && _inst._netSelfTest != null && _inst._netSelfTest.Value)
				{
					NetworkSelfTest.Arm();
				}
			}
			catch (Exception ex2)
			{
				FeatureModule.Log.LogError((object)("[Access] self-test failed to arm: " + ex2));
			}
		}

		public override string StatusDetail()
		{
			return "who=" + ((_who != null) ? _who.Value.ToString() : "?") + " announce=" + ((_announce != null) ? _announce.Value.ToString() : "?") + " rate=" + RateLimit + "/10s selfTest=" + (_selfTest != null && _selfTest.Value) + " networkSelfTest=" + (_netSelfTest != null && _netSelfTest.Value) + " smoothServer=" + (SmoothServerBridge.Available ? (SmoothServerBridge.Version + " (" + SmoothServerBridge.Rows().Count + " rows)") : "not installed") + "  " + TweakDoor.StatusLine();
		}
	}
	internal static class AccessSelfTest
	{
		private const double HoldSeconds = 45.0;

		private static bool _armed;

		private static bool _done;

		private static DateTime _undoAt;

		private static SettingInfo _target;

		private static string _original;

		public static bool Finished => _done;

		public static void Arm()
		{
			if (_armed || _done)
			{
				return;
			}
			_armed = true;
			try
			{
				Phase1();
			}
			catch (Exception ex)
			{
				_done = true;
				Log("FAILED to start: " + ex);
			}
		}

		public static void Pump()
		{
			if (!_armed || _done || DateTime.UtcNow < _undoAt)
			{
				return;
			}
			_done = true;
			try
			{
				Phase2();
			}
			catch (Exception ex)
			{
				Log("FAILED during undo: " + ex);
			}
		}

		private static void Phase1()
		{
			_target = PickTarget();
			if (_target == null)
			{
				_done = true;
				Log("no suitable setting to exercise - skipped");
				return;
			}
			string configFilePath = NoVikingLeftBehindPlugin.Cfg.ConfigFilePath;
			_original = CfgFile.GetValue(configFilePath, _target.Section, _target.Key);
			string text = Different(_target, _original);
			Log("target " + _target.Id + " (" + _target.TypeName + ", tier " + _target.Tier.ToString() + (_target.HasRange ? (", range " + N(_target.Min) + ".." + N(_target.Max)) : "") + ")");
			Log("before: file has " + _target.Key + " = " + _original + ", entry = " + _target.CurrentString);
			string message;
			bool flag = TweakDoor.ApplyOne(ZNet.GetUID(), _target.Section, _target.Key, text, out message);
			Log("door says: ok=" + flag + " \"" + message + "\"");
			if (!flag)
			{
				_done = true;
				Log("FAILED - the door refused a change the server itself made");
				return;
			}
			string value = CfgFile.GetValue(configFilePath, _target.Section, _target.Key);
			string currentString = _target.CurrentString;
			bool flag2 = value == text && currentString == text;
			Log("after set: file has " + value + ", entry = " + currentString + "  -> " + (flag2 ? "OK" : "MISMATCH"));
			if (!flag2)
			{
				_done = true;
				Log("FAILED - the file and the entry disagree");
				return;
			}
			Log("holding for " + 45 + "s so an operator can see it with `cfg.py get nvlb " + _target.Section + " " + _target.Key + "`, then undoing");
			_undoAt = DateTime.UtcNow.AddSeconds(45.0);
		}

		private static void Phase2()
		{
			string configFilePath = NoVikingLeftBehindPlugin.Cfg.ConfigFilePath;
			string message;
			bool flag = TweakDoor.Undo(ZNet.GetUID(), out message);
			Log("undo says: ok=" + flag + " \"" + message + "\"");
			string value = CfgFile.GetValue(configFilePath, _target.Section, _target.Key);
			string currentString = _target.CurrentString;
			bool flag2 = flag && value == _original && currentString == _original;
			Log("after undo: file has " + value + ", entry = " + currentString + " (was " + _original + ")  -> " + (flag2 ? "OK" : "MISMATCH"));
			Log(flag2 ? "PASSED - write, backup, live push and undo all work; the config is back where it started" : "FAILED - the value was not restored; check the .bak-* files next to the cfg");
			foreach (string item in TweakDoor.ServerAudit())
			{
				Log("audit | " + item);
			}
		}

		private static SettingInfo PickTarget()
		{
			SettingInfo settingInfo = ConfigCatalog.Find("Regrowth", "RegrowDays");
			if (Suitable(settingInfo))
			{
				return settingInfo;
			}
			foreach (SettingInfo item in ConfigCatalog.All)
			{
				if (Suitable(item))
				{
					return item;
				}
			}
			return null;
		}

		private static bool Suitable(SettingInfo s)
		{
			if (s != null && !s.IsLocal && s.Live && !s.IsEnabledToggle && s.Tier == SettingTier.Everyone && s.HasRange)
			{
				if (!(s.TypeName == "float"))
				{
					return s.TypeName == "int";
				}
				return true;
			}
			return false;
		}

		private static string Different(SettingInfo s, string current)
		{
			if (!double.TryParse(current, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				result = s.Min;
			}
			double num = ((s.Step > 0.0) ? s.Step : 1.0);
			double num2 = result - num;
			if (num2 < s.Min)
			{
				num2 = result + num;
			}
			if (num2 > s.Max)
			{
				num2 = s.Min;
			}
			if (s.TypeName == "int")
			{
				return ((long)Math.Round(num2)).ToString(CultureInfo.InvariantCulture);
			}
			return ((float)num2).ToString("R", CultureInfo.InvariantCulture);
		}

		private static string N(double d)
		{
			if (d != Math.Floor(d) || !(Math.Abs(d) < 1000000000000000.0))
			{
				return d.ToString("0.####", CultureInfo.InvariantCulture);
			}
			return ((long)d).ToString(CultureInfo.InvariantCulture);
		}

		private static void Log(string s)
		{
			NoVikingLeftBehindPlugin.Log.LogInfo((object)("[Access][SelfTest] " + s));
		}
	}
	internal static class CfgFile
	{
		private const int BackupKeep = 20;

		public static string SetValue(string path, string section, string key, string newValue)
		{
			List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>();
			list.Add(new KeyValuePair<string, string>(section + "." + key, newValue));
			return SetValues(path, list);
		}

		public static string SetValues(string path, IList<KeyValuePair<string, string>> edits)
		{
			if (edits == null || edits.Count == 0)
			{
				return null;
			}
			List<string> list = new List<string>(File.ReadAllLines(path, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)));
			int num = 0;
			foreach (KeyValuePair<string, string> edit in edits)
			{
				int num2 = edit.Key.IndexOf('.');
				if (num2 > 0)
				{
					string text = edit.Key.Substring(0, num2);
					string text2 = edit.Key.Substring(num2 + 1);
					int num3 = FindLine(list, text, text2);
					if (num3 < 0)
					{
						throw new Exception("[" + text + "] " + text2 + " is not in " + Path.GetFileName(path));
					}
					list[num3] = text2 + " = " + edit.Value;
					num++;
				}
			}
			if (num == 0)
			{
				return null;
			}
			string result = MakeBackup(path);
			string text3 = path + ".nvlbtmp";
			File.WriteAllText(text3, string.Join("\n", list.ToArray()) + "\n", new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
			File.Copy(text3, path, overwrite: true);
			try
			{
				File.Delete(text3);
			}
			catch
			{
			}
			return result;
		}

		public static string GetValue(string path, string section, string key)
		{
			string[] array = File.ReadAllLines(path, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
			int num = FindLine(array, section, key);
			if (num < 0)
			{
				return null;
			}
			int num2 = array[num].IndexOf('=');
			if (num2 >= 0)
			{
				return array[num].Substring(num2 + 1).Trim();
			}
			return null;
		}

		private static int FindLine(IList<string> lines, string section, string key)
		{
			bool flag = false;
			for (int i = 0; i < lines.Count; i++)
			{
				string text = lines[i].Trim();
				if (text.Length == 0 || text[0] == '#')
				{
					continue;
				}
				if (text[0] == '[' && text.EndsWith("]", StringComparison.Ordinal))
				{
					flag = string.Equals(text.Substring(1, text.Length - 2).Trim(), section, StringComparison.OrdinalIgnoreCase);
				}
				else if (flag)
				{
					int num = text.IndexOf('=');
					if (num > 0 && string.Equals(text.Substring(0, num).Trim(), key, StringComparison.OrdinalIgnoreCase))
					{
						return i;
					}
				}
			}
			return -1;
		}

		public static string MakeBackup(string path)
		{
			string text = DateTime.Now.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture);
			string text2 = path + ".bak-" + text;
			int num = 2;
			while (File.Exists(text2))
			{
				text2 = path + ".bak-" + text + "-" + num++.ToString(CultureInfo.InvariantCulture);
			}
			File.Copy(path, text2, overwrite: false);
			Prune(path);
			return text2;
		}

		private static void Prune(string path)
		{
			try
			{
				string directoryName = Path.GetDirectoryName(path);
				if (string.IsNullOrEmpty(directoryName))
				{
					return;
				}
				string[] files = Directory.GetFiles(directoryName, Path.GetFileName(path) + ".bak-*");
				if (files.Length <= 20)
				{
					return;
				}
				Array.Sort(files, (IComparer<string>?)StringComparer.Ordinal);
				for (int i = 0; i < files.Length - 20; i++)
				{
					try
					{
						File.Delete(files[i]);
					}
					catch
					{
					}
				}
			}
			catch
			{
			}
		}
	}
	internal static class NetworkSelfTest
	{
		private const double StepSeconds = 2.5;

		private static bool _armed;

		private static bool _done;

		private static int _step;

		private static DateTime _nextAt;

		private static SettingInfo _profile;

		private static SettingInfo _map;

		private static string _original;

		private static string _mapOriginal;

		private static int _backupsBefore;

		private static bool _failed;

		public static void Arm()
		{
			if (!_armed && !_done)
			{
				_armed = true;
				_nextAt = DateTime.UtcNow.AddSeconds(2.5);
			}
		}

		public static void Pump()
		{
			if (!_armed || _done || (AccessModule.SelfTestWanted && !AccessSelfTest.Finished) || DateTime.UtcNow < _nextAt)
			{
				return;
			}
			_nextAt = DateTime.UtcNow.AddSeconds(2.5);
			try
			{
				Step();
			}
			catch (Exception ex)
			{
				_done = true;
				Log("FAILED with an exception: " + ex);
			}
		}

		private static void Step()
		{
			switch (_step++)
			{
			case 0:
				Setup();
				break;
			case 1:
				Set("1/9", "Default");
				break;
			case 2:
				Set("2/9", "FastLink");
				break;
			case 3:
				Undo("3/9", "Default");
				break;
			case 4:
				Undo("4/9", _original);
				break;
			case 5:
				SecondSection("5/9");
				break;
			case 6:
				UndoSecondSection("6/9");
				break;
			case 7:
				RefusePresetDriven("7/9");
				break;
			case 8:
				RefuseNotOnTheAllowlist("8/9");
				break;
			case 9:
				RefuseNonAdmin("9/9");
				break;
			default:
				Finish();
				break;
			}
		}

		private static void Setup()
		{
			if (!SmoothServerBridge.Available)
			{
				_done = true;
				Log("SmoothServer is not installed on this server. Nothing to test - the panel does not exist here and the door would refuse every SmoothServer key with that same sentence.");
				return;
			}
			Log("SmoothServer " + SmoothServerBridge.Version + " found, config " + SmoothServerBridge.CfgPath);
			Log("allowlist (" + SmoothServerBridge.Rows().Count + " rows): " + SmoothServerBridge.AllowlistLine());
			_profile = SmoothServerBridge.Find("Profiles", "Profile");
			_map = SmoothServerBridge.Find("Map", "Enabled");
			if (_profile == null || _map == null)
			{
				_done = true;
				Log("FAILED - the allowlist did not resolve " + ((_profile == null) ? "[Profiles] Profile" : "[Map] Enabled"));
				return;
			}
			_original = OnDisk();
			_backupsBefore = Backups();
			Log("before: file has Profile = " + _original + ", entry = " + _profile.CurrentString + ", backups = " + _backupsBefore);
			if (_original != _profile.CurrentString)
			{
				Fail("the file and SmoothServer's live entry disagree before we touched anything");
			}
		}

		private static void Set(string label, string want)
		{
			string currentString = _profile.CurrentString;
			string message;
			bool flag = TweakDoor.ApplyOne(ZNet.GetUID(), "SS:Profiles", "Profile", want, out message);
			Log("step " + label + " set " + want + ": door ok=" + flag + " \"" + message + "\"");
			if (!flag)
			{
				Fail("the door refused a change the server itself made");
			}
			else
			{
				Verify(label, currentString, want);
			}
		}

		private static void Undo(string label, string want)
		{
			string currentString = _profile.CurrentString;
			string message;
			bool flag = TweakDoor.Undo(ZNet.GetUID(), out message);
			Log("step " + label + " undo: door ok=" + flag + " \"" + message + "\"");
			if (!flag)
			{
				Fail("the door refused an undo of its own change");
			}
			else
			{
				Verify(label, currentString, want);
			}
		}

		private static void Verify(string label, string before, string want)
		{
			string text = OnDisk();
			string currentString = _profile.CurrentString;
			int num = Backups();
			bool flag = text == want && currentString == want && num > _backupsBefore;
			Log("step " + label + " check: " + before + " -> file=" + text + " entry=" + currentString + " backups=" + num + "  -> " + (flag ? "OK" : "MISMATCH"));
			if (!flag)
			{
				Fail("expected " + want + " in both SmoothServer's cfg file and its live entry" + ((num > _backupsBefore) ? "" : ", and a new .bak- file next to it"));
			}
		}

		private static void SecondSection(string label)
		{
			_mapOriginal = _map.CurrentString;
			string text = (string.Equals(_mapOriginal, "true", StringComparison.OrdinalIgnoreCase) ? "false" : "true");
			string message;
			bool flag = TweakDoor.ApplyOne(ZNet.GetUID(), "SS:Map", "Enabled", text, out message);
			Log("step " + label + " a second section, [Map] Enabled -> " + text + ": ok=" + flag + " \"" + message + "\"");
			if (!flag)
			{
				Fail("the door refused a change to [Map] Enabled");
				return;
			}
			string value = CfgFile.GetValue(SmoothServerBridge.CfgPath, "Map", "Enabled");
			bool flag2 = value == text && _map.CurrentString == text;
			Log("step " + label + " check: file=" + value + " entry=" + _map.CurrentString + "  -> " + (flag2 ? "OK" : "MISMATCH"));
			if (!flag2)
			{
				Fail("[Map] Enabled did not reach both the file and the live entry");
			}
		}

		private static void UndoSecondSection(string label)
		{
			string message;
			bool flag = TweakDoor.Undo(ZNet.GetUID(), out message);
			string value = CfgFile.GetValue(SmoothServerBridge.CfgPath, "Map", "Enabled");
			bool flag2 = flag && value == _mapOriginal && _map.CurrentString == _mapOriginal;
			Log("step " + label + " undo it: ok=" + flag + " \"" + message + "\" file=" + value + " entry=" + _map.CurrentString + " (was " + _mapOriginal + ")  -> " + (flag2 ? "OK" : "MISMATCH"));
			if (!flag2)
			{
				Fail("[Map] Enabled was not put back");
			}
		}

		private static void RefusePresetDriven(string label)
		{
			string text = SmoothServerBridge.CurrentProfile();
			SettingInfo settingInfo = SmoothServerBridge.Find("Compression", "Enabled");
			string text2 = ((settingInfo != null) ? settingInfo.CurrentString : "?");
			string message;
			bool flag = TweakDoor.ApplyOne(ZNet.GetUID(), "SS:Compression", "Enabled", "false", out message);
			string text3 = ((settingInfo != null) ? settingInfo.CurrentString : "?");
			bool flag2 = !flag && text3 == text2;
			Log("step " + label + " a key the preset owns, switched off while Profile=" + text + " ([Compression] Enabled=false): ok=" + flag + " \"" + message + "\" still=" + text3 + "  -> " + (flag2 ? "OK" : "MISMATCH"));
			if (!flag2)
			{
				Fail("the door let a preset-driven key be changed to a value the preset would silently revert");
			}
		}

		private static void RefuseNotOnTheAllowlist(string label)
		{
			string message;
			bool flag = TweakDoor.ApplyOne(ZNet.GetUID(), "SS:AdaptiveBudget", "CeilingBytes", "262144", out message);
			Log("step " + label + " a SmoothServer key that is not on the allowlist (AdaptiveBudget.CeilingBytes, asked for by the server itself): ok=" + flag + " \"" + message + "\"  -> " + ((!flag) ? "OK" : "LEAK"));
			if (flag)
			{
				Fail("the door applied a SmoothServer key that is not on the allowlist");
			}
		}

		private static void RefuseNonAdmin(string label)
		{
			string message;
			bool flag = TweakDoor.ApplyOne(987654321L, "SS:General", "EnforceClientMod", "true", out message);
			Log("step " + label + " an admin-only row asked for by a non-admin caller (General.EnforceClientMod): ok=" + flag + " \"" + message + "\"  -> " + ((!flag) ? "OK" : "LEAK"));
			if (flag)
			{
				Fail("a non-admin changed an admin-only SmoothServer setting");
			}
		}

		private static void Finish()
		{
			_done = true;
			string text = OnDisk();
			string text2 = ((_profile != null) ? _profile.CurrentString : "?");
			string text3 = ((_map != null) ? _map.CurrentString : "?");
			bool flag = text == _original && text2 == _original && (_mapOriginal == null || text3 == _mapOriginal);
			Log("restored: file has Profile = " + text + ", entry = " + text2 + " (was " + _original + "); [Map] Enabled = " + text3 + " (was " + (_mapOriginal ?? "?") + ")  -> " + (flag ? "OK" : "MISMATCH"));
			if (!flag)
			{
				Fail("the original value was not restored - check the .bak-* files next to " + SmoothServerBridge.CfgPath);
			}
			Log(_failed ? "FAILED - see the MISMATCH/LEAK lines above" : "PASSED - the door writes SmoothServer's own cfg file with a backup, its watcher and ServerSync pick it up, undo puts it back, and everything off the allowlist is refused");
			foreach (string item in TweakDoor.ServerAudit())
			{
				Log("audit | " + item);
			}
		}

		private static string OnDisk()
		{
			try
			{
				return CfgFile.GetValue(SmoothServerBridge.CfgPath, "Profiles", "Profile");
			}
			catch (Exception ex)
			{
				return "unreadable (" + ex.Message + ")";
			}
		}

		private static int Backups()
		{
			try
			{
				string cfgPath = SmoothServerBridge.CfgPath;
				string directoryName = Path.GetDirectoryName(cfgPath);
				if (string.IsNullOrEmpty(directoryName))
				{
					return 0;
				}
				return Directory.GetFiles(directoryName, Path.GetFileName(cfgPath) + ".bak-*").Length;
			}
			catch
			{
				return 0;
			}
		}

		private static void Fail(string why)
		{
			_failed = true;
			Log("FAILED - " + why);
		}

		private static void Log(string s)
		{
			NoVikingLeftBehindPlugin.Log.LogInfo((object)("[Access][NetworkSelfTest] " + s));
		}
	}
	internal static class SettingValue
	{
		public static string Format(SettingInfo info, object value)
		{
			return ConfigCatalog.Serialize(value, info?.ValueType);
		}

		public static string TryParse(SettingInfo info, string raw, out object parsed)
		{
			parsed = null;
			if (info == null)
			{
				return "unknown setting";
			}
			if (raw == null)
			{
				return "no value";
			}
			raw = raw.Trim();
			switch (info.TypeName)
			{
			case "bool":
			{
				if (!TryParseBool(raw, out var value))
				{
					return "expected true or false";
				}
				parsed = value;
				return null;
			}
			case "int":
			{
				if (!double.TryParse(raw, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
				{
					return "expected a whole number";
				}
				long num = (long)Math.Round(result);
				string text3 = CheckRange(info, num);
				if (text3 != null)
				{
					return text3;
				}
				if (info.ValueType == typeof(long))
				{
					parsed = num;
				}
				else
				{
					if (num < int.MinValue || num > int.MaxValue)
					{
						return "number is too large";
					}
					parsed = (int)num;
				}
				return null;
			}
			case "float":
			{
				if (!double.TryParse(raw, NumberStyles.Float, CultureInfo.InvariantCulture, out var result2))
				{
					return "expected a number";
				}
				if (double.IsNaN(result2) || double.IsInfinity(result2))
				{
					return "expected a number";
				}
				string text5 = CheckRange(info, result2);
				if (text5 != null)
				{
					return text5;
				}
				parsed = ((info.ValueType == typeof(double)) ? ((object)result2) : ((object)(float)result2));
				return null;
			}
			case "enum":
			{
				string[] choices = Enum.GetNames(info.ValueType);
				foreach (string text4 in choices)
				{
					if (string.Equals(text4, raw, StringComparison.OrdinalIgnoreCase))
					{
						parsed = Enum.Parse(info.ValueType, text4);
						return null;
					}
				}
				return "expected one of: " + string.Join(", ", Enum.GetNames(info.ValueType));
			}
			case "string":
				raw = ConfigCatalog.Unquote(raw);
				if (raw.Length > 4000)
				{
					return "that value is too long";
				}
				if (raw.IndexOf('\n') >= 0 || raw.IndexOf('\r') >= 0)
				{
					return "a value cannot span more than one line";
				}
				if (info.Choices != null)
				{
					string[] choices = info.Choices;
					foreach (string text in choices)
					{
						if (string.Equals(text, raw, StringComparison.OrdinalIgnoreCase))
						{
							parsed = text;
							return null;
						}
					}
					return "expected one of: " + string.Join(", ", info.Choices);
				}
				if (info.Owner != null)
				{
					string text2 = null;
					try
					{
						text2 = info.Owner.ValidateValue(info, raw);
					}
					catch (Exception ex)
					{
						text2 = "could not be checked (" + ex.Message + ")";
					}
					if (!string.IsNullOrEmpty(text2))
					{
						return text2;
					}
				}
				parsed = raw;
				return null;
			default:
				return "this setting cannot be changed from the menu";
			}
		}

		private static string CheckRange(SettingInfo info, double d)
		{
			if (!info.HasRange)
			{
				return null;
			}
			if (d < info.Min || d > info.Max)
			{
				return "must be between " + Trim(info.Min) + " and " + Trim(info.Max);
			}
			return null;
		}

		private static string Trim(double d)
		{
			if (d != Math.Floor(d) || !(Math.Abs(d) < 1000000000000000.0))
			{
				return d.ToString("0.####", CultureInfo.InvariantCulture);
			}
			return ((long)d).ToString(CultureInfo.InvariantCulture);
		}

		public static bool TryParseBool(string raw, out bool value)
		{
			switch (raw.Trim().ToLowerInvariant())
			{
			case "1":
			case "on":
			case "yes":
			case "true":
				value = true;
				return true;
			case "0":
			case "no":
			case "off":
			case "false":
				value = false;
				return true;
			default:
				value = false;
				return false;
			}
		}
	}
	internal static class SmoothServerBridge
	{
		private sealed class Allowed
		{
			public string Section;

			public string Key;

			public string Label;

			public string Hint;

			public string TypeName;

			public string[] Choices;

			public string EnumTypeName;

			public SettingTier Tier;

			public bool Local;

			public string Extra;

			public string About;

			public bool ProfileDriven;

			public SettingLevel Level = SettingLevel.Advanced;

			public string SimpleGroup;
		}

		public const string Guid = "Nosferatu.SmoothServer";

		public const string Tag = "SS";

		public const string Prefix = "SS:";

		public const string NotInstalled = "SmoothServer is not installed on this server.";

		public const string NotAllowed = "That SmoothServer setting cannot be changed from this menu.";

		public const string PanelLabel = "Network (SmoothServer)";

		public const string PanelTheme = "Network";

		public const string PanelHint = "A few network switches, for when the server feels bad";

		private const int DescriptionCap = 900;

		private static readonly Allowed[] List = new Allowed[5]
		{
			new Allowed
			{
				Section = "Profiles",
				Key = "Profile",
				Label = "Network preset",
				TypeName = "enum",
				EnumTypeName = "NetProfile",
				Choices = new string[3] { "Default", "FastLink", "Custom" },
				Tier = SettingTier.Everyone,
				Level = SettingLevel.Essential,
				SimpleGroup = "Server & safety",
				Hint = "Default is the safe choice if people rubber-band; FastLink is for strong PCs on good connections",
				About = "Default: vanilla-like, and the safest choice if anyone is rubber-banding.\nFastLink: tuned for a small group on strong PCs and good connections - more updates a second and a bigger send budget.\nCustom: use whatever the server's own config file says.",
				Extra = "Everyone on the server runs the preset the server picks."
			},
			new Allowed
			{
				Section = "Compression",
				Key = "Enabled",
				Label = "Compression",
				TypeName = "bool",
				Tier = SettingTier.Everyone,
				ProfileDriven = true,
				Hint = "Squeeze the traffic between the server and everyone on it",
				About = "Shrinks the data sent between the server and the people playing on it. Leave it on unless somebody cannot connect.",
				Extra = "The network preset turns this on: to switch it off, set the preset to Custom first."
			},
			new Allowed
			{
				Section = "Map",
				Key = "Enabled",
				Label = "Shared map",
				TypeName = "bool",
				Tier = SettingTier.Everyone,
				Level = SettingLevel.Essential,
				SimpleGroup = "Server & safety",
				Hint = "Everyone explores the same map, and shares pins",
				About = "Everyone explores one shared map and sees each other's pins.",
				Extra = null
			},
			new Allowed
			{
				Section = "SmoothMotion",
				Key = "Enabled",
				Label = "Smooth motion (this PC)",
				TypeName = "bool",
				Tier = SettingTier.Everyone,
				Local = true,
				Hint = "How other players and mobs are drawn between updates, on your screen",
				About = "How other players and creatures are drawn between network updates, on YOUR screen only. Off is what vanilla does. Worth trying with a friend in view.",
				Extra = "Written to your own SmoothServer config and to nobody else's. It changes nothing that is sent, stored or simulated - only what your machine draws."
			},
			new Allowed
			{
				Section = "General",
				Key = "EnforceClientMod",
				Label = "Require the client mod",
				TypeName = "bool",
				Tier = SettingTier.Admin,
				Hint = "Only players who have SmoothServer installed may join",
				About = "Only players with SmoothServer installed can join. Off lets anyone in - they simply miss out on compression and the shared map.",
				Extra = "Also locks SmoothServer's shared settings to the server and its admins."
			}
		};

		private static ConfigFile _cfg;

		private static object _plugin;

		private static string _version;

		private static FieldInfo _processingServerUpdate;

		private static bool _searched;

		private static List<SettingInfo> _rows;

		private static readonly SettingInfo[] None = new SettingInfo[0];

		public static bool Available => Resolve() != null;

		public static string Version
		{
			get
			{
				Resolve();
				return _version ?? "?";
			}
		}

		public static string CfgPath
		{
			get
			{
				ConfigFile val = Resolve();
				if (val == null)
				{
					return null;
				}
				return val.ConfigFilePath;
			}
		}

		public static ConfigFile Cfg => Resolve();

		public static string GroupHeader => "SmoothServer " + Version;

		private static ConfigFile Resolve()
		{
			if (_cfg != null)
			{
				return _cfg;
			}
			try
			{
				if (!Chainloader.PluginInfos.TryGetValue("Nosferatu.SmoothServer", out var value) || value == null)
				{
					return null;
				}
				BaseUnityPlugin instance = value.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					return null;
				}
				ConfigFile config = instance.Config;
				if (config == null)
				{
					return null;
				}
				_plugin = instance;
				_cfg = config;
				_version = ((value.Metadata != null && value.Metadata.Version != null) ? value.Metadata.Version.ToString() : "?");
				if (!_searched)
				{
					_searched = true;
					NoVikingLeftBehindPlugin.Log.LogInfo((object)("[Network] SmoothServer " + _version + " is loaded here; its config is " + config.ConfigFilePath));
					try
					{
						string directoryName = Path.GetDirectoryName(NoVikingLeftBehindPlugin.Cfg.ConfigFilePath);
						string directoryName2 = Path.GetDirectoryName(config.ConfigFilePath);
						if (!string.Equals(directoryName, directoryName2, StringComparison.OrdinalIgnoreCase))
						{
							NoVikingLeftBehindPlugin.Log.LogWarning((object)("[Network] SmoothServer's config is not in the same directory as ours (" + directoryName2 + " vs " + directoryName + ")"));
						}
					}
					catch
					{
					}
				}
			}
			catch (Exception ex)
			{
				NoVikingLeftBehindPlugin.Log.LogWarning((object)("[Network] could not look SmoothServer up: " + ex.Message));
				return null;
			}
			return _cfg;
		}

		private static ConfigEntryBase Entry(string section, string key)
		{
			ConfigFile val = Resolve();
			if (val == null)
			{
				return null;
			}
			foreach (KeyValuePair<ConfigDefinition, ConfigEntryBase> item in val)
			{
				if (item.Key.Section == section && item.Key.Key == key)
				{
					return item.Value;
				}
			}
			return null;
		}

		public static IList<SettingInfo> Rows()
		{
			if (_rows != null)
			{
				return _rows;
			}
			if (Resolve() == null)
			{
				return None;
			}
			List<SettingInfo> list = new List<SettingInfo>();
			Allowed[] list2 = List;
			for (int i = 0; i < list2.Length; i++)
			{
				SettingInfo settingInfo = Build(list2[i]);
				if (settingInfo != null)
				{
					list.Add(settingInfo);
				}
			}
			_rows = list;
			NoVikingLeftBehindPlugin.Log.LogInfo((object)("[Network] panel: " + list.Count + " of " + List.Length + " allowlisted SmoothServer settings resolved" + ((list.Count == List.Length) ? "" : " - the rest are not in this build of SmoothServer")));
			return _rows;
		}

		private static SettingInfo Build(Allowed a)
		{
			ConfigEntryBase val = Entry(a.Section, a.Key);
			if (val == null)
			{
				NoVikingLeftBehindPlugin.Log.LogWarning((object)("[Network] SmoothServer has no [" + a.Section + "] " + a.Key + " - that row is dropped"));
				return null;
			}
			Type settingType = val.SettingType;
			if (a.TypeName == "bool" && settingType != typeof(bool))
			{
				Mismatch(a, settingType);
				return null;
			}
			if (a.TypeName == "enum" && (!settingType.IsEnum || settingType.Name != a.EnumTypeName || !SameNames(a.Choices, Enum.GetNames(settingType))))
			{
				Mismatch(a, settingType);
				return null;
			}
			return new SettingInfo
			{
				Section = a.Section,
				Key = a.Key,
				Label = a.Label,
				Hint = a.Hint,
				Description = Describe(a, val),
				Entry = val,
				ValueType = settingType,
				TypeName = a.TypeName,
				Choices = a.Choices,
				Tier = a.Tier,
				Live = true,
				IsLocal = a.Local,
				FreeText = false,
				Owner = null,
				ModuleName = "SmoothServer",
				Theme = "Network",
				ModuleHint = "A few network switches, for when the server feels bad",
				IsEnabledToggle = false,
				ForeignTag = "SS",
				Level = a.Level,
				SimpleGroup = a.SimpleGroup,
				SimpleOrder = 50
			};
		}

		private static void Mismatch(Allowed a, Type actual)
		{
			NoVikingLeftBehindPlugin.Log.LogWarning((object)("[Network] SmoothServer's [" + a.Section + "] " + a.Key + " is a " + actual.Name + ", not the " + a.TypeName + " this panel expects - that row is dropped rather than guessed at"));
		}

		private static bool SameNames(string[] want, string[] got)
		{
			if (want == null || got == null || want.Length != got.Length)
			{
				return false;
			}
			foreach (string a in want)
			{
				bool flag = false;
				foreach (string b in got)
				{
					if (string.Equals(a, b, StringComparison.Ordinal))
					{
						flag = true;
						break;
					}
				}
				if (!flag)
				{
					return false;
				}
			}
			return true;
		}

		private static string Describe(Allowed a, ConfigEntryBase entry)
		{
			string text = "This is a SmoothServer setting, shown here so it can be changed in game." + (string.IsNullOrEmpty(a.Extra) ? "" : (" " + a.Extra));
			if (!string.IsNullOrEmpty(a.About))
			{
				return a.About + "\n\n" + text;
			}
			string text2 = null;
			try
			{
				text2 = ((entry.Description != null) ? entry.Description.Description : null);
			}
			catch
			{
			}
			if (!string.IsNullOrEmpty(text2) && text2.Length > 900)
			{
				int num = text2.LastIndexOf(". ", 900, StringComparison.Ordinal);
				text2 = ((num > 450) ? text2.Substring(0, num + 1) : text2.Substring(0, 900).TrimEnd(Array.Empty<char>())) + " ...";
			}
			if (!string.IsNullOrEmpty(text2))
			{
				return text2 + "\n\n" + text;
			}
			return text;
		}

		private static Allowed Lookup(string section, string key)
		{
			Allowed[] list = List;
			foreach (Allowed allowed in list)
			{
				if (string.Equals(allowed.Section, section, StringComparison.OrdinalIgnoreCase) && string.Equals(allowed.Key, key, StringComparison.OrdinalIgnoreCase))
				{
					return allowed;
				}
			}
			return null;
		}

		public static SettingInfo Find(string section, string key)
		{
			if (string.IsNullOrEmpty(section) || string.IsNullOrEmpty(key))
			{
				return null;
			}
			foreach (SettingInfo item in Rows())
			{
				if (string.Equals(item.Section, section, StringComparison.OrdinalIgnoreCase) && string.Equals(item.Key, key, StringComparison.OrdinalIgnoreCase))
				{
					return item;
				}
			}
			return null;
		}

		public static SettingInfo FindById(string id)
		{
			if (string.IsNullOrEmpty(id) || !id.StartsWith("SS:", StringComparison.Ordinal))
			{
				return null;
			}
			string text = id.Substring("SS:".Length);
			int num = text.IndexOf('.');
			if (num <= 0 || num == text.Length - 1)
			{
				return null;
			}
			return Find(text.Substring(0, num), text.Substring(num + 1));
		}

		public static bool IsForeignSection(string section)
		{
			return section?.StartsWith("SS:", StringComparison.Ordinal) ?? false;
		}

		public static string StripPrefix(string section)
		{
			if (!IsForeignSection(section))
			{
				return section;
			}
			return section.Substring("SS:".Length);
		}

		public static string Validate(SettingInfo info, string raw)
		{
			if (info == null)
			{
				return "That SmoothServer setting cannot be changed from this menu.";
			}
			Allowed allowed = Lookup(info.Section, info.Key);
			if (allowed == null)
			{
				return "That SmoothServer setting cannot be changed from this menu.";
			}
			if (allowed.Choices == null)
			{
				return null;
			}
			string b = (raw ?? "").Trim();
			string[] choices = allowed.Choices;
			for (int i = 0; i < choices.Length; i++)
			{
				if (string.Equals(choices[i], b, StringComparison.OrdinalIgnoreCase))
				{
					return null;
				}
			}
			return info.Label + ": expected one of " + string.Join(", ", allowed.Choices) + ".";
		}

		public static string ProfileRefusal(SettingInfo info, string newValue)
		{
			if (info == null)
			{
				return null;
			}
			Allowed allowed = Lookup(info.Section, info.Key);
			if (allowed == null || !allowed.ProfileDriven)
			{
				return null;
			}
			string text = CurrentProfile();
			if (text == null || string.Equals(text, "Custom", StringComparison.OrdinalIgnoreCase))
			{
				return null;
			}
			if (SettingValue.TryParseBool(newValue ?? "", out var value) && value)
			{
				return null;
			}
			return "The network preset (" + text + ") sets " + info.Label + ". Change the preset to Custom first.";
		}

		public static string RestartRefusal(SettingInfo info, string newValue)
		{
			string text = StatusForOn(info, newValue);
			if (text == null)
			{
				return null;
			}
			if (text.StartsWith("FAILED", StringComparison.Ordinal))
			{
				return "SmoothServer could not start that feature on this server.";
			}
			if (string.Equals(text, "disabled", StringComparison.Ordinal))
			{
				return "Needs a server restart.";
			}
			return null;
		}

		public static string LocalRestartNote(SettingInfo info, string newValue)
		{
			string text = StatusForOn(info, newValue);
			if (text == null)
			{
				return null;
			}
			if (text.StartsWith("FAILED", StringComparison.Ordinal))
			{
				return "SmoothServer could not start that feature on this machine.";
			}
			if (string.Equals(text, "disabled", StringComparison.Ordinal))
			{
				return "Saved - restart Valheim for it to take effect.";
			}
			return null;
		}

		private static string StatusForOn(SettingInfo info, string newValue)
		{
			if (info == null || !string.Equals(info.Key, "Enabled", StringComparison.Ordinal))
			{
				return null;
			}
			if (!SettingValue.TryParseBool(newValue ?? "", out var value) || !value)
			{
				return null;
			}
			return ModuleStatus(info.Section);
		}

		private static string ModuleStatus(string section)
		{
			try
			{
				if (Resolve() == null || _plugin == null)
				{
					return null;
				}
				FieldInfo field = _plugin.GetType().GetField("Modules", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				IEnumerable enumerable = ((field != null) ? (field.GetValue(null) as IEnumerable) : null);
				if (enumerable == null)
				{
					return null;
				}
				foreach (object item in enumerable)
				{
					if (item != null)
					{
						Type type = item.GetType();
						PropertyInfo property = type.GetProperty("Section", BindingFlags.Instance | BindingFlags.Public);
						if (string.Equals((property != null) ? (property.GetValue(item, null) as string) : null, section, StringComparison.Ordinal))
						{
							FieldInfo field2 = type.GetField("Status", BindingFlags.Instance | BindingFlags.Public);
							return (field2 != null) ? (field2.GetValue(item) as string) : null;
						}
					}
				}
			}
			catch (Exception ex)
			{
				NoVikingLeftBehindPlugin.Log.LogWarning((object)("[Network] could not read SmoothServer's module status for [" + section + "]: " + ex.Message));
			}
			return null;
		}

		public static string CurrentProfile()
		{
			return Find("Profiles", "Profile")?.CurrentString;
		}

		public static void Write(SettingInfo info, object parsed, string canonical, bool perPlayer)
		{
			string text = CfgFile.SetValue((Resolve() ?? throw new Exception("SmoothServer is not installed on this server.")).ConfigFilePath, info.Section, info.Key, canonical);
			bool flag = perPlayer && SuppressBroadcast(on: true);
			try
			{
				info.Entry.BoxedValue = parsed;
			}
			finally
			{
				if (flag)
				{
					SuppressBroadcast(on: false);
				}
			}
			NoVikingLeftBehindPlugin.Log.LogInfo((object)("[Network] wrote SmoothServer [" + info.Section + "] " + info.Key + " = " + canonical + "  (backup " + text + ")"));
		}

		private static bool SuppressBroadcast(bool on)
		{
			try
			{
				if (_processingServerUpdate == null)
				{
					if (_plugin == null)
					{
						return false;
					}
					Type type = _plugin.GetType().Assembly.GetType("ServerSync.ConfigSync");
					if (type == null)
					{
						return false;
					}
					_processingServerUpdate = type.GetField("ProcessingServerUpdate", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
					if (_processingServerUpdate == null || _processingServerUpdate.FieldType != typeof(bool))
					{
						_processingServerUpdate = null;
						return false;
					}
				}
				_processingServerUpdate.SetValue(null, on);
				return true;
			}
			catch
			{
				return false;
			}
		}

		public static void RequestResetToDefault()
		{
			Ask("Profiles", "Profile", "Default");
			Ask("Compression", "Enabled", "true");
			Ask("Map", "Enabled", "true");
		}

		private static void Ask(string section, string key, string value)
		{
			SettingInfo settingInfo = Find(section, key);
			if (settingInfo != null)
			{
				TweakDoor.Request(settingInfo, value);
			}
		}

		public static string AllowlistLine()
		{
			List<string> list = new List<string>();
			Allowed[] list2 = List;
			foreach (Allowed allowed in list2)
			{
				list.Add(allowed.Section + "." + allowed.Key + "(" + allowed.TypeName + ((allowed.Choices != null) ? (" " + string.Join("|", allowed.Choices)) : "") + (allowed.Local ? " per-player" : (" " + allowed.Tier)) + ")");
			}
			return string.Join(", ", list.ToArray());
		}
	}
	internal static class TweakDoor
	{
		private sealed class Change
		{
			public string Id;

			public string OldValue;

			public string NewValue;

			public string Actor;

			public DateTime When;
		}

		public const string RpcTweak = "NVLB_Tweak";

		public const string RpcUndo = "NVLB_Undo";

		public const string RpcReset = "NVLB_ResetModule";

		public const string RpcAuditReq = "NVLB_AuditRequest";

		public const string RpcResult = "NVLB_TweakResult";

		public const string RpcAudit = "NVLB_Audit";

		private const int RateWindowSeconds = 10;

		private const int UndoDepthPerKey = 20;

		private const int UndoDepthTotal = 100;

		private const int AuditKeep = 20;

		public const int AuditShown = 5;

		private static readonly Dictionary<long, List<DateTime>> _rate = new Dictionary<long, List<DateTime>>();

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

		private static readonly List<Change> _undoOrder = new List<Change>();

		private static readonly List<string> _audit = new List<string>();

		private static object _registeredOn;

		private static readonly List<string> _clientAudit = new List<string>();

		public static IList<string> AuditLines => _clientAudit;

		public static event Action<bool, string> Result;

		public static event Action AuditChanged;

		public static void RegisterRpcs()
		{
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance == null || _registeredOn == instance)
			{
				return;
			}
			_registeredOn = instance;
			_rate.Clear();
			_undoByKey.Clear();
			_undoOrder.Clear();
			_audit.Clear();
			_clientAudit.Clear();
			try
			{
				instance.Register<string, string, string>("NVLB_Tweak", (Action<long, string, string, string>)RPC_Tweak);
				instance.Register<string>("NVLB_Undo", (Action<long, string>)RPC_Undo);
				instance.Register<string>("NVLB_ResetModule", (Action<long, string>)RPC_ResetModule);
				instance.Register("NVLB_AuditRequest", (Action<long>)RPC_AuditRequest);
				instance.Register<int, string>("NVLB_TweakResult", (Action<long, int, string>)RPC_Result);
				instance.Register<string>("NVLB_Audit", (Action<long, string>)RPC_Audit);
				Log("registered RPCs NVLB_Tweak / NVLB_Undo / NVLB_ResetModule / NVLB_AuditRequest / NVLB_TweakResult / NVLB_Audit");
			}
			catch (Exception ex)
			{
				_registeredOn = null;
				NoVikingLeftBehindPlugin.Log.LogError((object)("[Access] could not register RPCs: " + ex));
			}
		}

		private static void Log(string s)
		{
			NoVikingLeftBehindPlugin.Log.LogInfo((object)("[Access] " + s));
		}

		public static void Request(SettingInfo info, string value)
		{
			if (info != null)
			{
				if (info.IsLocal)
				{
					ApplyLocal(info, value);
					return;
				}
				if (ZRoutedRpc.instance == null)
				{
					Fire(ok: false, "Not connected to a server.");
					return;
				}
				ZRoutedRpc.instance.InvokeRoutedRPC("NVLB_Tweak", new object[3] { info.WireSection, info.Key, value });
			}
		}

		public static void RequestUndo()
		{
			if (ZRoutedRpc.instance == null)
			{
				Fire(ok: false, "Not connected to a server.");
				return;
			}
			ZRoutedRpc.instance.InvokeRoutedRPC("NVLB_Undo", new object[1] { "last" });
		}

		public static void RequestResetModule(string section)
		{
			if (ZRoutedRpc.instance == null)
			{
				Fire(ok: false, "Not connected to a server.");
				return;
			}
			ZRoutedRpc.instance.InvokeRoutedRPC("NVLB_ResetModule", new object[1] {