Decompiled source of LethalConfigSync v0.0.2

plugins/bobabulkerENTERPRISE.LethalConfigSync.dll

Decompiled 18 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using LethalConfigSync.ConfigSync;
using Microsoft.CodeAnalysis;
using Unity.Collections;
using Unity.Netcode;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("bobabulkerENTERPRISE.LethalConfigSync")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.0.2.0")]
[assembly: AssemblyInformationalVersion("0.0.2+4c80ae880dbd424d7e9da593a096f56c58a0cfe9")]
[assembly: AssemblyProduct("LethalConfigSync")]
[assembly: AssemblyTitle("bobabulkerENTERPRISE.LethalConfigSync")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.2.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

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

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace LethalConfigSync
{
	[BepInPlugin("bobabulkerENTERPRISE.LethalConfigSync", "LethalConfigSync", "0.0.2")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public static Plugin Instance { get; private set; }

		internal static ManualLogSource Logger { get; private set; }

		internal static Harmony? Harmony { get; set; }

		private void Awake()
		{
			Logger = ((BaseUnityPlugin)this).Logger;
			Instance = this;
			SyncSettings.Init(((BaseUnityPlugin)this).Config);
			if (!SyncSettings.HasRules)
			{
				Logger.LogInfo((object)"SyncedEntries is empty, so as a host LethalConfigSync will not sync anything. Set it in the config to choose what to sync.");
			}
			Patch();
			Logger.LogInfo((object)"bobabulkerENTERPRISE.LethalConfigSync v0.0.2 [BETA] has loaded!");
		}

		internal static void Patch()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Expected O, but got Unknown
			if (Harmony == null)
			{
				Harmony = new Harmony("bobabulkerENTERPRISE.LethalConfigSync");
			}
			Logger.LogDebug((object)"Patching...");
			Harmony.PatchAll();
			Logger.LogDebug((object)"Finished patching!");
		}

		internal static void Unpatch()
		{
			Logger.LogDebug((object)"Unpatching...");
			Harmony? harmony = Harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			Logger.LogDebug((object)"Finished unpatching!");
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "bobabulkerENTERPRISE.LethalConfigSync";

		public const string PLUGIN_NAME = "LethalConfigSync";

		public const string PLUGIN_VERSION = "0.0.2";
	}
}
namespace LethalConfigSync.Patches
{
	[HarmonyPatch]
	internal static class NetworkPatches
	{
		[HarmonyPatch(typeof(NetworkManager), "Initialize", new Type[] { typeof(bool) })]
		[HarmonyPostfix]
		private static void RegisterHandlerPostfix(NetworkManager __instance, bool server)
		{
			HostRules.Clear();
			HeadBobSync.Restore();
			ConfigSyncNetwork.ResetSession();
			if (!server)
			{
				ConfigSyncNetwork.RegisterHandler(__instance);
			}
		}

		[HarmonyPatch(typeof(NetworkManager), "Shutdown")]
		[HarmonyPostfix]
		private static void ShutdownPostfix()
		{
			HostRules.Clear();
			HeadBobSync.Restore();
			ConfigSyncNetwork.ResetSession();
		}

		[HarmonyPatch(typeof(StartOfRound), "OnClientConnect")]
		[HarmonyPostfix]
		private static void SendConfigPostfix(ulong clientId)
		{
			ConfigSyncNetwork.SendToClient(clientId);
		}

		[HarmonyPatch(typeof(GameNetworkManager), "SetConnectionDataBeforeConnecting")]
		[HarmonyPostfix]
		private static void AppendMarkerPostfix()
		{
			JoinCheck.AppendMarker();
		}

		[HarmonyPatch(typeof(GameNetworkManager), "ConnectionApproval")]
		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void ConnectionApprovalPostfix(ConnectionApprovalRequest request, ConnectionApprovalResponse response)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			JoinCheck.Enforce(request, response);
		}
	}
}
namespace LethalConfigSync.ConfigSync
{
	internal static class ConfigSyncNetwork
	{
		private sealed class PendingMod
		{
			public string Guid { get; }

			public bool Found { get; set; }

			public int Changed { get; set; }

			public int Rejected { get; set; }

			public HashSet<ConfigFile> ChangedFiles { get; } = new HashSet<ConfigFile>();

			public PendingMod(string guid)
			{
				Guid = guid;
			}
		}

		[CompilerGenerated]
		private static class <>O
		{
			public static HandleNamedMessageDelegate <0>__OnRulesMessage;

			public static HandleNamedMessageDelegate <1>__OnConfigMessage;
		}

		private const string ConfigMessageName = "bobabulkerENTERPRISE.LethalConfigSync.ConfigSync";

		private const string RulesMessageName = "bobabulkerENTERPRISE.LethalConfigSync.Rules";

		internal const int ProtocolVersion = 2;

		private const int MaxEntriesPerChunk = 4096;

		private const int ChunkBudgetBytes = 4096;

		private static PendingMod? _pending;

		private static bool _warnedProtocol;

		internal static void ResetSession()
		{
			_pending = null;
		}

		internal static void SendToClient(ulong clientId)
		{
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)singleton == (Object)null || !singleton.IsServer || singleton.CustomMessagingManager == null || clientId == singleton.LocalClientId)
			{
				return;
			}
			try
			{
				SendRules(singleton, clientId);
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"Failed to send the rules to client {clientId}: {arg}");
			}
			if (!SyncSettings.HasRules)
			{
				Plugin.Logger.LogDebug((object)$"SyncedEntries is empty, so client {clientId} has nothing to sync.");
				return;
			}
			int num = 0;
			int num2 = 0;
			foreach (ModConfig item in LethalConfigSource.GetAll())
			{
				try
				{
					int num3 = SendMod(singleton, item, clientId);
					if (num3 > 0)
					{
						num++;
						num2 += num3;
					}
				}
				catch (Exception arg2)
				{
					Plugin.Logger.LogError((object)$"Failed to send {item.Guid} config to client {clientId}: {arg2}");
				}
			}
			if (num2 == 0)
			{
				Plugin.Logger.LogWarning((object)"SyncedEntries matched no settings in LethalConfig, so nothing was sent. Check the mod GUIDs, sections and keys.");
			}
			else
			{
				Plugin.Logger.LogInfo((object)$"Sent {num2} settings from {num} mods to client {clientId}.");
			}
		}

		internal static void SendToAllClients()
		{
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)singleton == (Object)null || !singleton.IsServer)
			{
				Plugin.Logger.LogInfo((object)"SyncNow only works for the host, in a lobby. Nothing was sent.");
				return;
			}
			List<ulong> list = new List<ulong>(singleton.ConnectedClientsIds);
			int num = 0;
			foreach (ulong item in list)
			{
				if (item != singleton.LocalClientId)
				{
					SendToClient(item);
					num++;
				}
			}
			if (num == 0)
			{
				Plugin.Logger.LogInfo((object)"SyncNow: there are no other players in the lobby.");
			}
		}

		private unsafe static void SendRules(NetworkManager nm, ulong clientId)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			string syncedEntries = SyncSettings.SyncedEntries;
			byte b = HeadBobSync.HostState();
			int num = 4 + FastBufferWriter.GetWriteSize(syncedEntries, false) + 1;
			FastBufferWriter val = default(FastBufferWriter);
			((FastBufferWriter)(ref val))..ctor(num, (Allocator)2, -1);
			try
			{
				int num2 = 2;
				((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref num2, default(ForPrimitives));
				((FastBufferWriter)(ref val)).WriteValueSafe(syncedEntries, false);
				((FastBufferWriter)(ref val)).WriteValueSafe<byte>(ref b, default(ForPrimitives));
				nm.CustomMessagingManager.SendNamedMessage("bobabulkerENTERPRISE.LethalConfigSync.Rules", clientId, val, (NetworkDelivery)4);
			}
			finally
			{
				((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose();
			}
		}

		private static int SendMod(NetworkManager nm, ModConfig mod, ulong clientId)
		{
			List<(string, string, string)> list = new List<(string, string, string)>();
			foreach (ConfigEntryBase entry in mod.Entries)
			{
				if (SyncSettings.IsSelected(mod.Guid, entry))
				{
					list.Add((entry.Definition.Section, entry.Definition.Key, entry.GetSerializedValue()));
				}
			}
			if (list.Count == 0)
			{
				return 0;
			}
			int num = 16 + FastBufferWriter.GetWriteSize(mod.Guid, false);
			List<(int, int, int)> list2 = new List<(int, int, int)>();
			int num2 = 0;
			while (num2 < list.Count)
			{
				int num3 = num;
				int i;
				for (i = num2; i < list.Count && i - num2 < 4096; i++)
				{
					int num4 = ItemSize(list[i]);
					if (i > num2 && num3 + num4 > 4096)
					{
						break;
					}
					num3 += num4;
				}
				list2.Add((num2, i, num3));
				num2 = i;
			}
			for (int j = 0; j < list2.Count; j++)
			{
				WriteChunk(nm, clientId, mod.Guid, list, list2[j].Item1, list2[j].Item2, list2[j].Item3, j, list2.Count);
			}
			return list.Count;
		}

		private static int ItemSize((string Section, string Key, string Value) item)
		{
			return FastBufferWriter.GetWriteSize(item.Section, false) + FastBufferWriter.GetWriteSize(item.Key, false) + FastBufferWriter.GetWriteSize(item.Value, false);
		}

		private unsafe static void WriteChunk(NetworkManager nm, ulong clientId, string guid, List<(string Section, string Key, string Value)> items, int start, int end, int size, int chunkIndex, int chunkCount)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			FastBufferWriter val = default(FastBufferWriter);
			((FastBufferWriter)(ref val))..ctor(size, (Allocator)2, -1);
			try
			{
				int num = 2;
				int num2 = end - start;
				((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref num, default(ForPrimitives));
				((FastBufferWriter)(ref val)).WriteValueSafe(guid, false);
				((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref chunkIndex, default(ForPrimitives));
				((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref chunkCount, default(ForPrimitives));
				((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref num2, default(ForPrimitives));
				for (int i = start; i < end; i++)
				{
					((FastBufferWriter)(ref val)).WriteValueSafe(items[i].Section, false);
					((FastBufferWriter)(ref val)).WriteValueSafe(items[i].Key, false);
					((FastBufferWriter)(ref val)).WriteValueSafe(items[i].Value, false);
				}
				nm.CustomMessagingManager.SendNamedMessage("bobabulkerENTERPRISE.LethalConfigSync.ConfigSync", clientId, val, (NetworkDelivery)4);
			}
			finally
			{
				((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose();
			}
		}

		internal static void RegisterHandler(NetworkManager nm)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Expected O, but got Unknown
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Expected O, but got Unknown
			CustomMessagingManager customMessagingManager = nm.CustomMessagingManager;
			if (customMessagingManager != null)
			{
				object obj = <>O.<0>__OnRulesMessage;
				if (obj == null)
				{
					HandleNamedMessageDelegate val = OnRulesMessage;
					<>O.<0>__OnRulesMessage = val;
					obj = (object)val;
				}
				customMessagingManager.RegisterNamedMessageHandler("bobabulkerENTERPRISE.LethalConfigSync.Rules", (HandleNamedMessageDelegate)obj);
			}
			CustomMessagingManager customMessagingManager2 = nm.CustomMessagingManager;
			if (customMessagingManager2 != null)
			{
				object obj2 = <>O.<1>__OnConfigMessage;
				if (obj2 == null)
				{
					HandleNamedMessageDelegate val2 = OnConfigMessage;
					<>O.<1>__OnConfigMessage = val2;
					obj2 = (object)val2;
				}
				customMessagingManager2.RegisterNamedMessageHandler("bobabulkerENTERPRISE.LethalConfigSync.ConfigSync", (HandleNamedMessageDelegate)obj2);
			}
		}

		private static bool VersionMatches(int version)
		{
			if (version == 2)
			{
				return true;
			}
			if (!_warnedProtocol)
			{
				_warnedProtocol = true;
				Plugin.Logger.LogWarning((object)$"Host sent config with protocol {version}; this client speaks {2}. Update LethalConfigSync so you and the host match.");
			}
			return false;
		}

		private static void OnRulesMessage(ulong senderClientId, FastBufferReader reader)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			if (senderClientId != 0L)
			{
				return;
			}
			try
			{
				int version = default(int);
				((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref version, default(ForPrimitives));
				if (VersionMatches(version))
				{
					string text = default(string);
					((FastBufferReader)(ref reader)).ReadValueSafe(ref text, false);
					byte state = default(byte);
					((FastBufferReader)(ref reader)).ReadValueSafe<byte>(ref state, default(ForPrimitives));
					HostRules.Apply(text);
					HeadBobSync.Apply(state);
					Plugin.Logger.LogInfo((object)((text.Length == 0) ? "The host syncs nothing." : ("The host syncs: " + text)));
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"Failed to process the host's rules: {arg}");
			}
		}

		private static void OnConfigMessage(ulong senderClientId, FastBufferReader reader)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			if (senderClientId != 0L)
			{
				return;
			}
			try
			{
				int version = default(int);
				((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref version, default(ForPrimitives));
				if (!VersionMatches(version))
				{
					return;
				}
				string text = default(string);
				((FastBufferReader)(ref reader)).ReadValueSafe(ref text, false);
				int num = default(int);
				((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num, default(ForPrimitives));
				int num2 = default(int);
				((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num2, default(ForPrimitives));
				int num3 = default(int);
				((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num3, default(ForPrimitives));
				if (num3 < 0 || num3 > 4096 || num2 < 1 || num < 0 || num >= num2)
				{
					Plugin.Logger.LogWarning((object)("Ignoring malformed config chunk for '" + text + "'."));
					return;
				}
				Dictionary<(string, string), string> dictionary = new Dictionary<(string, string), string>(num3);
				string item = default(string);
				string item2 = default(string);
				string value = default(string);
				for (int i = 0; i < num3; i++)
				{
					((FastBufferReader)(ref reader)).ReadValueSafe(ref item, false);
					((FastBufferReader)(ref reader)).ReadValueSafe(ref item2, false);
					((FastBufferReader)(ref reader)).ReadValueSafe(ref value, false);
					dictionary[(item, item2)] = value;
				}
				ApplyChunk(text, dictionary, num, num2);
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"Failed to process host config sync: {arg}");
			}
		}

		private static void ApplyChunk(string modGuid, Dictionary<(string Section, string Key), string> incoming, int chunkIndex, int chunkCount)
		{
			if (SyncSettings.IsProtectedMod(modGuid))
			{
				Plugin.Logger.LogDebug((object)("Ignoring config for protected mod '" + modGuid + "'."));
				return;
			}
			if (chunkIndex == 0 || _pending == null || !string.Equals(_pending.Guid, modGuid, StringComparison.OrdinalIgnoreCase))
			{
				_pending = new PendingMod(modGuid);
			}
			PendingMod pending = _pending;
			List<ConfigEntryBase> entries = LethalConfigSource.GetEntries(modGuid);
			if (entries.Count > 0)
			{
				pending.Found = true;
			}
			Dictionary<ConfigFile, bool> dictionary = new Dictionary<ConfigFile, bool>();
			try
			{
				foreach (ConfigEntryBase item in entries)
				{
					if (!incoming.TryGetValue((item.Definition.Section, item.Definition.Key), out string value))
					{
						continue;
					}
					try
					{
						ConfigFile configFile = item.ConfigFile;
						if (!dictionary.ContainsKey(configFile))
						{
							dictionary[configFile] = configFile.SaveOnConfigSet;
							configFile.SaveOnConfigSet = false;
						}
						if (!(item.GetSerializedValue() == value))
						{
							item.SetSerializedValue(value);
							if (item.GetSerializedValue() == value)
							{
								pending.Changed++;
								pending.ChangedFiles.Add(configFile);
								continue;
							}
							pending.Rejected++;
							Plugin.Logger.LogDebug((object)("[" + modGuid + "] host value for [" + item.Definition.Section + "] " + item.Definition.Key + " was not accepted as-is: '" + value + "'."));
						}
					}
					catch (Exception ex)
					{
						bool flag = false;
						try
						{
							flag = item.GetSerializedValue() == value;
						}
						catch (Exception)
						{
						}
						if (flag)
						{
							pending.Changed++;
							pending.ChangedFiles.Add(item.ConfigFile);
						}
						else
						{
							pending.Rejected++;
						}
						Plugin.Logger.LogWarning((object)("[" + modGuid + "] error while applying [" + item.Definition.Section + "] " + item.Definition.Key + ": " + ex.Message));
					}
				}
			}
			finally
			{
				foreach (KeyValuePair<ConfigFile, bool> item2 in dictionary)
				{
					item2.Key.SaveOnConfigSet = item2.Value;
				}
			}
			if (chunkIndex == chunkCount - 1)
			{
				Finish(pending);
			}
		}

		private static void Finish(PendingMod pending)
		{
			foreach (ConfigFile changedFile in pending.ChangedFiles)
			{
				try
				{
					changedFile.Save();
				}
				catch (Exception ex)
				{
					Plugin.Logger.LogError((object)("Could not save " + changedFile.ConfigFilePath + ": " + ex.Message));
				}
			}
			if (!pending.Found)
			{
				Plugin.Logger.LogDebug((object)("[" + pending.Guid + "] is not listed in LethalConfig on this client; skipped."));
			}
			else if (pending.Changed > 0 || pending.Rejected > 0)
			{
				Plugin.Logger.LogInfo((object)$"[{pending.Guid}] synced with host: {pending.Changed} changed, {pending.Rejected} rejected.");
			}
			else
			{
				Plugin.Logger.LogDebug((object)("[" + pending.Guid + "] already matches the host."));
			}
			_pending = null;
		}
	}
	internal static class HeadBobSync
	{
		private const byte NotSynced = 0;

		private const byte Off = 1;

		private const byte On = 2;

		private static bool? _original;

		private static bool _lastApplied;

		internal static byte HostState()
		{
			if (!SyncSettings.SyncHeadBobbing)
			{
				return 0;
			}
			try
			{
				IngamePlayerSettings instance = IngamePlayerSettings.Instance;
				if ((Object)(object)instance == (Object)null || instance.settings == null)
				{
					Plugin.Logger.LogWarning((object)"SyncHeadBobbing is on, but the game's settings are not available, so head bobbing was not synced.");
					return 0;
				}
				return (byte)((!instance.settings.headBobbing) ? 1 : 2);
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("Could not read the game's head bobbing setting: " + ex.Message));
				return 0;
			}
		}

		internal static void Apply(byte state)
		{
			if (state == 0)
			{
				Restore();
				return;
			}
			try
			{
				IngamePlayerSettings instance = IngamePlayerSettings.Instance;
				if ((Object)(object)instance == (Object)null || instance.settings == null)
				{
					Plugin.Logger.LogWarning((object)"The host syncs head bobbing, but the game's settings are not available yet.");
					return;
				}
				bool flag = state == 2;
				bool headBobbing = instance.settings.headBobbing;
				if (!_original.HasValue || headBobbing != _lastApplied)
				{
					_original = headBobbing;
				}
				if (headBobbing != flag)
				{
					instance.settings.headBobbing = flag;
					Plugin.Logger.LogInfo((object)("Head bobbing set by the host: " + (flag ? "on" : "off") + " (yours was " + (headBobbing ? "on" : "off") + ")."));
				}
				_lastApplied = flag;
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("Could not apply the host's head bobbing setting: " + ex.Message));
			}
		}

		internal static void Restore()
		{
			if (!_original.HasValue)
			{
				return;
			}
			try
			{
				IngamePlayerSettings instance = IngamePlayerSettings.Instance;
				if ((Object)(object)instance != (Object)null && instance.settings != null && instance.settings.headBobbing == _lastApplied)
				{
					instance.settings.headBobbing = _original.Value;
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("Could not restore your head bobbing setting: " + ex.Message));
			}
			finally
			{
				_original = null;
			}
		}
	}
	internal static class HostRules
	{
		private const string NothingText = "(the host syncs nothing)";

		private static bool _lockInstalled;

		internal static bool IsActive { get; private set; }

		internal static void Apply(string hostValue)
		{
			ConfigEntry<string> hostSyncedEntriesEntry = SyncSettings.HostSyncedEntriesEntry;
			hostSyncedEntriesEntry.Value = (string.IsNullOrWhiteSpace(hostValue) ? "(the host syncs nothing)" : hostValue);
			IsActive = true;
			if (!_lockInstalled)
			{
				_lockInstalled = LethalConfigSource.TryLock((ConfigEntryBase)(object)hostSyncedEntriesEntry, "Display only. This is the host's list.", () => true);
			}
		}

		internal static void Clear()
		{
			if (IsActive)
			{
				SyncSettings.HostSyncedEntriesEntry.Value = string.Empty;
				IsActive = false;
			}
		}
	}
	internal static class JoinCheck
	{
		private const string MarkerPrefix = "LethalConfigSync/";

		internal static void AppendMarker()
		{
			try
			{
				NetworkManager singleton = NetworkManager.Singleton;
				if (!((Object)(object)singleton == (Object)null))
				{
					string text = Encoding.ASCII.GetString(singleton.NetworkConfig.ConnectionData ?? Array.Empty<byte>());
					if (!text.Contains("LethalConfigSync/"))
					{
						singleton.NetworkConfig.ConnectionData = Encoding.ASCII.GetBytes(text + ",LethalConfigSync/" + 2);
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"Could not add the LethalConfigSync marker to the connection data: {arg}");
			}
		}

		internal static void Enforce(ConnectionApprovalRequest request, ConnectionApprovalResponse response)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				NetworkManager singleton = NetworkManager.Singleton;
				if (!((Object)(object)singleton == (Object)null) && request.ClientNetworkId != singleton.LocalClientId && response.Approved)
				{
					string text = CheckPayload(request.Payload);
					if (text.Length != 0)
					{
						response.Approved = false;
						response.Reason = text;
						Plugin.Logger.LogWarning((object)$"Refused client {request.ClientNetworkId}: {text}");
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"Join check failed, leaving the connection as the game decided: {arg}");
			}
		}

		private static string CheckPayload(byte[]? payloadBytes)
		{
			string text = Encoding.ASCII.GetString(payloadBytes ?? Array.Empty<byte>());
			string[] array = text.Split(',');
			foreach (string text2 in array)
			{
				if (text2.StartsWith("LethalConfigSync/", StringComparison.Ordinal))
				{
					if (int.TryParse(text2.Substring("LethalConfigSync/".Length), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) && result == 2)
					{
						return string.Empty;
					}
					return "LethalConfigSync version mismatch. The host has 0.0.2; update yours to match.";
				}
			}
			return "This lobby requires the LethalConfigSync mod (version 0.0.2).";
		}
	}
	internal sealed class ModConfig
	{
		public string Guid { get; }

		public IReadOnlyList<ConfigEntryBase> Entries { get; }

		public ModConfig(string guid, IReadOnlyList<ConfigEntryBase> entries)
		{
			Guid = guid;
			Entries = entries;
		}
	}
	internal static class LethalConfigSource
	{
		internal const string PluginGuid = "ainavt.lc.lethalconfig";

		private static bool _resolved;

		private static bool _available;

		private static bool _prepared;

		private static PropertyInfo? _modsProperty;

		private static MethodInfo? _generateMissing;

		private static MethodInfo? _runLate;

		private static FieldInfo? _configItemsField;

		private static FieldInfo? _modInfoField;

		private static PropertyInfo? _guidProperty;

		private static PropertyInfo? _entryProperty;

		private static PropertyInfo? _optionsProperty;

		internal static List<ModConfig> GetAll()
		{
			List<ModConfig> list = new List<ModConfig>();
			foreach (var item3 in ListMods())
			{
				string item = item3.Guid;
				object item2 = item3.Mod;
				List<ConfigEntryBase> list2 = ReadEntries(item2);
				if (list2.Count > 0)
				{
					list.Add(new ModConfig(item, list2));
				}
			}
			return list;
		}

		internal static List<ConfigEntryBase> GetEntries(string guid)
		{
			List<ConfigEntryBase> list = new List<ConfigEntryBase>();
			foreach (var (a, mod) in ListMods())
			{
				if (string.Equals(a, guid, StringComparison.OrdinalIgnoreCase))
				{
					list.AddRange(ReadEntries(mod));
				}
			}
			return list;
		}

		internal static bool TryLock(ConfigEntryBase target, string reason, Func<bool> isLocked)
		{
			if (!Resolve() || _optionsProperty == null)
			{
				return false;
			}
			bool result = false;
			try
			{
				foreach (var (a, obj) in ListMods())
				{
					if (!string.Equals(a, "bobabulkerENTERPRISE.LethalConfigSync", StringComparison.OrdinalIgnoreCase) || !(_configItemsField.GetValue(obj) is IEnumerable enumerable))
					{
						continue;
					}
					foreach (object item in enumerable)
					{
						if (item != null && _entryProperty.GetValue(item) == target)
						{
							object value = _optionsProperty.GetValue(item);
							PropertyInfo propertyInfo = ((value == null) ? null : AccessTools.Property(value.GetType(), "CanModifyCallback"));
							Delegate obj2 = ((propertyInfo == null) ? null : BuildCanModify(propertyInfo.PropertyType, reason, isLocked));
							if (value != null && !(propertyInfo == null) && (object)obj2 != null)
							{
								propertyInfo.SetValue(value, obj2);
								result = true;
							}
						}
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogDebug((object)("Could not lock the LethalConfig control: " + ex.Message));
			}
			return result;
		}

		private static Delegate? BuildCanModify(Type delegateType, string reason, Func<bool> isLocked)
		{
			MethodInfo method = delegateType.GetMethod("Invoke");
			if (method == null || method.GetParameters().Length != 0)
			{
				return null;
			}
			Type returnType = method.ReturnType;
			MethodInfo method2 = returnType.GetMethod("False", new Type[1] { typeof(string) });
			MethodInfo method3 = returnType.GetMethod("True", Type.EmptyTypes);
			if (method2 == null || method3 == null)
			{
				return null;
			}
			Expression body = Expression.Condition(Expression.Invoke(Expression.Constant(isLocked)), Expression.Call(method2, Expression.Constant(reason)), Expression.Call(method3));
			return Expression.Lambda(delegateType, body).Compile();
		}

		private static List<(string Guid, object Mod)> ListMods()
		{
			List<(string, object)> list = new List<(string, object)>();
			if (!Resolve())
			{
				return list;
			}
			Prepare();
			try
			{
				if (!(_modsProperty.GetValue(null) is IDictionary dictionary))
				{
					return list;
				}
				foreach (object value2 in dictionary.Values)
				{
					if (value2 == null)
					{
						continue;
					}
					try
					{
						object value = _modInfoField.GetValue(value2);
						string text = ((value == null) ? null : (_guidProperty.GetValue(value) as string));
						if (!string.IsNullOrEmpty(text))
						{
							list.Add((text, value2));
						}
					}
					catch (Exception ex)
					{
						Plugin.Logger.LogDebug((object)("Skipping a LethalConfig mod entry that could not be read: " + ex.Message));
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"Could not read LethalConfig's mod list: {arg}");
			}
			return list;
		}

		private static List<ConfigEntryBase> ReadEntries(object mod)
		{
			List<ConfigEntryBase> list = new List<ConfigEntryBase>();
			if (!(_configItemsField.GetValue(mod) is IEnumerable enumerable))
			{
				return list;
			}
			foreach (object item in enumerable)
			{
				if (item == null)
				{
					continue;
				}
				try
				{
					object? value = _entryProperty.GetValue(item);
					ConfigEntryBase val = (ConfigEntryBase)((value is ConfigEntryBase) ? value : null);
					if (val != null)
					{
						list.Add(val);
					}
				}
				catch (Exception)
				{
				}
			}
			return list;
		}

		private static void Prepare()
		{
			if (_prepared)
			{
				return;
			}
			_prepared = true;
			try
			{
				_generateMissing?.Invoke(null, null);
				_runLate?.Invoke(null, null);
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogDebug((object)("LethalConfig auto-generation could not be triggered early: " + ex.Message));
			}
		}

		private static bool Resolve()
		{
			if (_resolved)
			{
				return _available;
			}
			_resolved = true;
			try
			{
				if (!Chainloader.PluginInfos.TryGetValue("ainavt.lc.lethalconfig", out var value) || (Object)(object)value.Instance == (Object)null)
				{
					Plugin.Logger.LogWarning((object)"LethalConfig is not loaded; there is nothing to sync.");
					return false;
				}
				Assembly assembly = ((object)value.Instance).GetType().Assembly;
				Type type = assembly.GetType("LethalConfig.LethalConfigManager");
				Type type2 = assembly.GetType("LethalConfig.Mods.Mod");
				Type type3 = assembly.GetType("LethalConfig.Mods.ModInfo");
				Type type4 = assembly.GetType("LethalConfig.ConfigItems.BaseConfigItem");
				if (type == null || type2 == null || type3 == null || type4 == null)
				{
					Plugin.Logger.LogWarning((object)"LethalConfig's internal layout was not recognised (a different version?); config sync is disabled.");
					return false;
				}
				_modsProperty = AccessTools.Property(type, "Mods");
				_generateMissing = AccessTools.Method(type, "AutoGenerateMissingConfigsIfNeeded", (Type[])null, (Type[])null);
				_runLate = AccessTools.Method(type, "RunLateAutoGeneration", (Type[])null, (Type[])null);
				_configItemsField = AccessTools.Field(type2, "ConfigItems");
				_modInfoField = AccessTools.Field(type2, "ModInfo");
				_guidProperty = AccessTools.Property(type3, "Guid");
				_entryProperty = AccessTools.Property(type4, "BaseConfigEntry");
				_optionsProperty = AccessTools.Property(type4, "Options");
				_available = _modsProperty != null && _configItemsField != null && _modInfoField != null && _guidProperty != null && _entryProperty != null;
				if (!_available)
				{
					Plugin.Logger.LogWarning((object)"LethalConfig's internal layout was not recognised (a different version?); config sync is disabled.");
				}
				return _available;
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"Could not inspect LethalConfig: {arg}");
				return false;
			}
		}
	}
	internal static class SyncSettings
	{
		private sealed class EntryPattern
		{
			public Regex Guid { get; }

			public Regex Section { get; }

			public Regex Key { get; }

			public EntryPattern(Regex guid, Regex section, Regex key)
			{
				Guid = guid;
				Section = section;
				Key = key;
			}
		}

		private static readonly HashSet<string> ProtectedMods = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "bobabulkerENTERPRISE.LethalConfigSync", "ainavt.lc.lethalconfig" };

		private static ConfigEntry<string> _syncedEntries = null;

		private static ConfigEntry<bool> _syncNow = null;

		private static ConfigEntry<bool> _syncHeadBobbing = null;

		private static ConfigEntry<string> _hostSyncedEntries = null;

		private static string? _compiledFrom;

		private static List<EntryPattern> _patterns = new List<EntryPattern>();

		internal static ConfigEntry<string> HostSyncedEntriesEntry => _hostSyncedEntries;

		internal static bool SyncHeadBobbing => _syncHeadBobbing.Value;

		internal static string SyncedEntries => _syncedEntries.Value ?? string.Empty;

		internal static bool HasRules => GetPatterns().Count > 0;

		internal static void Init(ConfigFile config)
		{
			_syncedEntries = config.Bind<string>("Sync", "SyncedEntries", "", "Mods and settings the host syncs to players who join. Empty syncs nothing. Separate entries with ';'. Use a mod GUID, or 'GUID|section|setting' for a single setting. Example: com.seeya.cameraoverhaul; LethalHUD; MrHat.EladsLethalHUD; ShaosilGaming.GeneralImprovements|Mechanics|AddHealthRechargeStation");
			_syncNow = config.Bind<bool>("Sync", "SyncNow", false, "Host only. Check this and apply it to send your current settings to the players already in the lobby. It unchecks itself afterwards. Players who join later are synced automatically.");
			_syncHeadBobbing = config.Bind<bool>("Sync", "SyncHeadBobbing", false, "Host only. Players in your lobby get the same head bobbing setting as yours (the game's own setting, not a mod's). It is not saved to their game, and their own value comes back when they leave. Sent when a player joins and on SyncNow.");
			if (_syncNow.Value)
			{
				_syncNow.Value = false;
			}
			_syncNow.SettingChanged += OnSyncNowChanged;
			_hostSyncedEntries = config.Bind<string>("Host", "HostSyncedEntries", "", "Display only. Shows the host's SyncedEntries while you are in their lobby, and is empty otherwise. Your own SyncedEntries is never changed by a host. Editing this does nothing.");
			if (!string.IsNullOrEmpty(_hostSyncedEntries.Value))
			{
				_hostSyncedEntries.Value = string.Empty;
			}
		}

		private static void OnSyncNowChanged(object sender, EventArgs e)
		{
			if (!_syncNow.Value)
			{
				return;
			}
			try
			{
				ConfigSyncNetwork.SendToAllClients();
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"SyncNow failed: {arg}");
			}
			finally
			{
				_syncNow.Value = false;
			}
		}

		internal static bool IsProtectedMod(string modGuid)
		{
			return ProtectedMods.Contains(modGuid);
		}

		internal static bool IsSelected(string modGuid, ConfigEntryBase entry)
		{
			if (IsProtectedMod(modGuid))
			{
				return false;
			}
			string section = entry.Definition.Section;
			string key = entry.Definition.Key;
			foreach (EntryPattern pattern in GetPatterns())
			{
				if (pattern.Guid.IsMatch(modGuid) && pattern.Section.IsMatch(section) && pattern.Key.IsMatch(key))
				{
					return true;
				}
			}
			return false;
		}

		private static List<EntryPattern> GetPatterns()
		{
			string syncedEntries = SyncedEntries;
			if (string.Equals(syncedEntries, _compiledFrom, StringComparison.Ordinal))
			{
				return _patterns;
			}
			List<EntryPattern> list = new List<EntryPattern>();
			string[] array = syncedEntries.Split(';');
			foreach (string text in array)
			{
				string text2 = text.Trim();
				if (text2.Length != 0)
				{
					string[] array2 = text2.Split('|');
					if (array2.Length > 3)
					{
						Plugin.Logger.LogWarning((object)("Ignoring SyncedEntries pattern '" + text2 + "': use at most 'guid|section|key'."));
						continue;
					}
					string pattern = Field(array2, 0);
					string pattern2 = Field(array2, 1);
					string pattern3 = Field(array2, 2);
					list.Add(new EntryPattern(Wildcard(pattern), Wildcard(pattern2), Wildcard(pattern3)));
				}
			}
			_patterns = list;
			_compiledFrom = syncedEntries;
			return _patterns;
		}

		private static string Field(string[] fields, int index)
		{
			if (index >= fields.Length)
			{
				return "*";
			}
			string text = fields[index].Trim();
			if (text.Length != 0)
			{
				return text;
			}
			return "*";
		}

		private static Regex Wildcard(string pattern)
		{
			return new Regex("^" + Regex.Escape(pattern).Replace("\\*", ".*") + "$", RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.CultureInvariant);
		}
	}
}