Decompiled source of ConfigSync v0.1.1

plugins/tech.rethunk.mortar.ConfigSync.dll

Decompiled 15 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
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 BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Microsoft.CodeAnalysis;
using MonoMod.RuntimeDetour;
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("Rethunk Tech")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright (c) 2026 Rethunk Tech")]
[assembly: AssemblyFileVersion("0.1.1.0")]
[assembly: AssemblyInformationalVersion("0.1.1+32d9a5c5e1e73e0f1d7e9b2a25a589b132657c60")]
[assembly: AssemblyProduct("tech.rethunk.mortar.ConfigSync")]
[assembly: AssemblyTitle("ConfigSync")]
[assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/Rethunk-Tech/mortar-lc-configsync")]
[assembly: InternalsVisibleTo("ConfigSync.Tests")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.1.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 ConfigSync
{
	internal static class LethalConfigNotes
	{
		private sealed class Note
		{
			private readonly ISyncedEntry shared;

			public Note(ISyncedEntry shared)
			{
				this.shared = shared;
			}

			public string? Reason()
			{
				return shared.HostNote;
			}

			public Delegate For(Type delegateType)
			{
				Type returnType = delegateType.GetMethod("Invoke").ReturnType;
				ParameterExpression parameterExpression = Expression.Variable(typeof(string), "reason");
				Expression body = Expression.Block(returnType, new ParameterExpression[1] { parameterExpression }, Expression.Assign(parameterExpression, Expression.Call(Expression.Constant(this), "Reason", null)), Expression.Condition(Expression.Equal(parameterExpression, Expression.Constant(null, typeof(string))), Expression.Call(returnType, "True", null), Expression.Call(returnType, "False", null, parameterExpression)));
				return Expression.Lambda(delegateType, body).Compile();
			}
		}

		private const BindingFlags Hidden = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		private static Hook? generateHook;

		private static Hook? originalValueHook;

		private static Hook? appearanceHook;

		private static Type? managerType;

		private static FieldInfo? controllerItemField;

		private static FieldInfo? tooltipField;

		private static PropertyInfo? entryProperty;

		private static PropertyInfo? currentValueProperty;

		private static PropertyInfo? originalValueProperty;

		public static void Attach()
		{
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Expected O, but got Unknown
			//IL_013e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0148: Expected O, but got Unknown
			Type manager = Type.GetType("LethalConfig.LethalConfigManager, LethalConfig");
			MethodInfo methodInfo = manager?.GetMethod("AutoGenerateMissingConfigsIfNeeded", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			Type type = Type.GetType("LethalConfig.ConfigItems.BaseConfigItem, LethalConfig");
			entryProperty = type?.GetProperty("BaseConfigEntry", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			currentValueProperty = type?.GetProperty("CurrentBoxedValue", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			originalValueProperty = type?.GetProperty("OriginalBoxedValue", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (manager == null || methodInfo == null || entryProperty == null || currentValueProperty == null || originalValueProperty?.GetMethod == null)
			{
				return;
			}
			managerType = manager;
			HookTooltips();
			SyncedConfig.StateChanged += Push;
			originalValueHook = new Hook((MethodBase)originalValueProperty.GetMethod, (Delegate)(Func<Func<object, object>, object, object>)delegate(Func<object, object> orig, object self)
			{
				object? value = entryProperty.GetValue(self);
				ConfigEntryBase val = (ConfigEntryBase)((value is ConfigEntryBase) ? value : null);
				if (val != null)
				{
					object obj = SyncedConfig.Find(val)?.HostValue;
					if (obj != null)
					{
						return obj;
					}
				}
				return orig(self);
			});
			generateHook = new Hook((MethodBase)methodInfo, (Delegate)(Action<Action>)delegate(Action orig)
			{
				orig();
				try
				{
					Mark(manager);
				}
				catch (Exception ex)
				{
					ManualLogSource? source = Log.Source;
					if (source != null)
					{
						source.LogWarning((object)("Couldn't mark shared settings in LethalConfig: " + ex.Message));
					}
				}
			});
		}

		private static void Mark(Type manager)
		{
			foreach (var (obj, shared) in SharedItems(manager))
			{
				currentValueProperty.SetValue(obj, originalValueProperty.GetValue(obj));
				object obj2 = obj.GetType().GetProperty("Options", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj);
				PropertyInfo propertyInfo = obj2?.GetType().GetProperty("CanModifyCallback");
				if (!(propertyInfo == null) && propertyInfo.GetValue(obj2) == null)
				{
					propertyInfo.SetValue(obj2, new Note(shared).For(propertyInfo.PropertyType));
				}
			}
		}

		private static IEnumerable<(object Item, ISyncedEntry Shared)> SharedItems(Type manager)
		{
			if (!(manager.GetProperty("Mods", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null) is IDictionary dictionary))
			{
				yield break;
			}
			foreach (object value2 in dictionary.Values)
			{
				if (!(value2.GetType().GetField("ConfigItems", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(value2) is IEnumerable enumerable))
				{
					continue;
				}
				foreach (object item in enumerable)
				{
					object? value = entryProperty.GetValue(item);
					ConfigEntryBase val = (ConfigEntryBase)((value is ConfigEntryBase) ? value : null);
					if (val != null)
					{
						ISyncedEntry syncedEntry = SyncedConfig.Find(val);
						if (syncedEntry != null)
						{
							yield return (Item: item, Shared: syncedEntry);
						}
					}
				}
			}
		}

		private static void Push(ISyncedEntry changed)
		{
			if (managerType == null)
			{
				return;
			}
			try
			{
				foreach (var (obj, syncedEntry) in SharedItems(managerType))
				{
					if (syncedEntry == changed)
					{
						currentValueProperty.SetValue(obj, originalValueProperty.GetValue(obj));
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource? source = Log.Source;
				if (source != null)
				{
					source.LogWarning((object)("Couldn't update LethalConfig with the host's value: " + ex.Message));
				}
			}
		}

		private static void HookTooltips()
		{
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Expected O, but got Unknown
			Type type = Type.GetType("LethalConfig.MonoBehaviours.Components.ModConfigController, LethalConfig");
			controllerItemField = type?.GetField("BaseConfigItem", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			tooltipField = type?.GetField("tooltipTrigger", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			MethodInfo methodInfo = type?.GetMethod("UpdateAppearance", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (controllerItemField == null || tooltipField?.FieldType.GetField("tooltipText", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) == null || methodInfo == null)
			{
				return;
			}
			appearanceHook = new Hook((MethodBase)methodInfo, (Delegate)(Action<Action<object>, object>)delegate(Action<object> orig, object self)
			{
				orig(self);
				try
				{
					object value = controllerItemField.GetValue(self);
					if (value != null)
					{
						object? value2 = entryProperty.GetValue(value);
						ConfigEntryBase val = (ConfigEntryBase)((value2 is ConfigEntryBase) ? value2 : null);
						if (val != null)
						{
							string text = SyncedConfig.Find(val)?.HostNote;
							if (text != null)
							{
								object value3 = tooltipField.GetValue(self);
								value3?.GetType().GetField("tooltipText", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).SetValue(value3, "<b>Modifying this entry is currently disabled:</b>\n<b>" + text + "</b>");
							}
						}
					}
				}
				catch (Exception ex)
				{
					ManualLogSource? source = Log.Source;
					if (source != null)
					{
						source.LogWarning((object)("Couldn't update a LethalConfig tooltip: " + ex.Message));
					}
				}
			});
		}
	}
	internal static class Log
	{
		internal static ManualLogSource? Source { get; set; }
	}
	internal static class Network
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static Action <0>__ServerStarted;

			public static Action <1>__ClientStarted;

			public static Action <2>__Flush;

			public static SceneEventDelegate <3>__SceneEvent;

			public static Action<ulong> <4>__SendTo;
		}

		private const string Prefix = "tech.rethunk.mortar.ConfigSync/";

		private const byte ProtocolVersion = 1;

		private const int MaxMessageBytes = 65536;

		private static NetworkManager? manager;

		private static readonly HashSet<string> warned = new HashSet<string>();

		internal static void Attach(NetworkManager networkManager)
		{
			if (!((Object)(object)manager == (Object)(object)networkManager))
			{
				manager = networkManager;
				networkManager.OnServerStarted += ServerStarted;
				networkManager.OnClientStarted += ClientStarted;
				networkManager.OnClientStopped += delegate
				{
					SyncedConfig.ClearAll();
					Notice.Reset();
				};
			}
		}

		private static void ServerStarted()
		{
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Expected O, but got Unknown
			if ((Object)(object)manager == (Object)null)
			{
				return;
			}
			manager.NetworkTickSystem.Tick += Flush;
			if (manager.SceneManager != null)
			{
				NetworkSceneManager sceneManager = manager.SceneManager;
				object obj = <>O.<3>__SceneEvent;
				if (obj == null)
				{
					SceneEventDelegate val = SceneEvent;
					<>O.<3>__SceneEvent = val;
					obj = (object)val;
				}
				sceneManager.OnSceneEvent += (SceneEventDelegate)obj;
			}
			else
			{
				manager.OnClientConnectedCallback += SendTo;
			}
		}

		private static void ClientStarted()
		{
			if ((Object)(object)manager == (Object)null || manager.IsServer)
			{
				return;
			}
			foreach (SyncedConfig item in SyncedConfig.All)
			{
				Listen(item);
			}
		}

		internal static void Created(SyncedConfig config)
		{
			if ((Object)(object)manager != (Object)null && manager.IsListening && manager.IsClient && !manager.IsServer)
			{
				Listen(config);
			}
		}

		private static void Listen(SyncedConfig config)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Expected O, but got Unknown
			SyncedConfig target = config;
			manager.CustomMessagingManager.RegisterNamedMessageHandler("tech.rethunk.mortar.ConfigSync/" + config.Id, (HandleNamedMessageDelegate)delegate(ulong _, FastBufferReader reader)
			{
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				Receive(target, reader);
			});
		}

		private static void SceneEvent(SceneEvent sceneEvent)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Invalid comparison between Unknown and I4
			if ((int)sceneEvent.SceneEventType == 2)
			{
				SendTo(sceneEvent.ClientId);
			}
		}

		private static void SendTo(ulong clientId)
		{
			if ((Object)(object)manager == (Object)null || !manager.IsServer || clientId == 0L)
			{
				return;
			}
			foreach (SyncedConfig item in SyncedConfig.All)
			{
				Send(item, new ulong[1] { clientId });
			}
		}

		private static void Flush()
		{
			if ((Object)(object)manager == (Object)null || !manager.IsServer)
			{
				return;
			}
			ulong[] array = manager.ConnectedClientsIds.Where((ulong id) => id != 0).ToArray();
			foreach (SyncedConfig item in SyncedConfig.All.Where((SyncedConfig c) => c.Dirty))
			{
				item.Dirty = false;
				if (array.Length != 0)
				{
					Send(item, array);
				}
			}
		}

		private unsafe static void Send(SyncedConfig config, IReadOnlyList<ulong> clientIds)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			if (config.Entries.Count == 0 || (Object)(object)manager == (Object)null)
			{
				return;
			}
			try
			{
				FastBufferWriter val = default(FastBufferWriter);
				((FastBufferWriter)(ref val))..ctor(256, (Allocator)2, 65536);
				try
				{
					byte b = 1;
					((FastBufferWriter)(ref val)).WriteValueSafe<byte>(ref b, default(ForPrimitives));
					((FastBufferWriter)(ref val)).WriteValueSafe(config.Version, false);
					int count = config.Entries.Count;
					((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref count, default(ForPrimitives));
					foreach (KeyValuePair<string, ISyncedEntry> entry in config.Entries)
					{
						((FastBufferWriter)(ref val)).WriteValueSafe(entry.Key, false);
						((FastBufferWriter)(ref val)).WriteValueSafe(entry.Value.LocalText, false);
					}
					manager.CustomMessagingManager.SendNamedMessage("tech.rethunk.mortar.ConfigSync/" + config.Id, clientIds, val, (NetworkDelivery)4);
				}
				finally
				{
					((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose();
				}
			}
			catch (Exception ex)
			{
				WarnOnce(config.Id + "/send", config.Id + ": couldn't send its shared settings: " + ex.Message);
			}
		}

		private static void Receive(SyncedConfig config, FastBufferReader reader)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			byte b = default(byte);
			((FastBufferReader)(ref reader)).ReadValueSafe<byte>(ref b, default(ForPrimitives));
			if (b != 1)
			{
				WarnOnce(config.Id + "/protocol", config.Id + ": the host runs a different ConfigSync version, so its settings aren't used");
				return;
			}
			string text = default(string);
			((FastBufferReader)(ref reader)).ReadValueSafe(ref text, false);
			if (text != config.Version && WarnOnce(config.Id + "/" + text, config.Id + ": the host has version " + text + " and you have " + config.Version))
			{
				Notice.Add(config.Name + ": the host has version " + text + " and you have " + config.Version + ".");
			}
			int num = default(int);
			((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num, default(ForPrimitives));
			string text2 = default(string);
			string text3 = default(string);
			for (int i = 0; i < num; i++)
			{
				((FastBufferReader)(ref reader)).ReadValueSafe(ref text2, false);
				((FastBufferReader)(ref reader)).ReadValueSafe(ref text3, false);
				if (!config.Entries.TryGetValue(text2, out ISyncedEntry value))
				{
					continue;
				}
				try
				{
					string hostText = value.HostText;
					value.Apply(text3);
					if (hostText != null && hostText != text3 && value.HostNote != null)
					{
						Notice.Add("The host set " + config.Name + ": " + text2.Substring(text2.IndexOf('\n') + 1) + " to " + text3 + ".");
					}
				}
				catch (Exception)
				{
					WarnOnce(config.Id + "/" + text2, config.Id + ": couldn't read the host's value for " + text2.Replace('\n', '.'));
				}
			}
		}

		private static bool WarnOnce(string key, string message)
		{
			if (!warned.Add(key))
			{
				return false;
			}
			ManualLogSource? source = Log.Source;
			if (source != null)
			{
				source.LogWarning((object)message);
			}
			return true;
		}
	}
	internal sealed class Notice : MonoBehaviour
	{
		private static readonly List<string> pending = new List<string>();

		private static bool joinShown;

		internal static void Add(string line)
		{
			pending.Add(line);
		}

		internal static void Reset()
		{
			pending.Clear();
			joinShown = false;
		}

		private void Update()
		{
			HUDManager instance = HUDManager.Instance;
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)instance == (Object)null || (Object)(object)singleton == (Object)null || singleton.IsServer || !singleton.IsConnectedClient || (Object)(object)GameNetworkManager.Instance?.localPlayerController == (Object)null)
			{
				return;
			}
			if (!joinShown)
			{
				joinShown = true;
				List<SyncedConfig> source = SyncedConfig.All.Where((SyncedConfig c) => c.Entries.Values.Any((ISyncedEntry e) => e.HostNote != null)).ToList();
				int num = source.Sum((SyncedConfig c) => c.Entries.Values.Count((ISyncedEntry e) => e.HostNote != null));
				if (num > 0)
				{
					pending.Insert(0, string.Format("The host sets {0} of your settings ({1}).", num, string.Join(", ", source.Select((SyncedConfig c) => c.Name))));
				}
			}
			foreach (string item in pending)
			{
				instance.AddChatMessage(item, "", -1, false);
			}
			pending.Clear();
		}
	}
	[BepInPlugin("tech.rethunk.mortar.ConfigSync", "ConfigSync", "0.1.1")]
	[BepInProcess("Lethal Company.exe")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		private void Awake()
		{
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Expected O, but got Unknown
			Log.Source = ((BaseUnityPlugin)this).Logger;
			ConfigEntry<bool> followRecommendations = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Follow host recommendations", true, "Use the host's value for settings a mod shares as a recommendation. Settings the host enforces apply either way.");
			SyncedConfig.FollowRecommendations = followRecommendations.Value;
			followRecommendations.SettingChanged += delegate
			{
				SyncedConfig.FollowRecommendations = followRecommendations.Value;
				SyncedConfig.RefreshAll();
			};
			NetworkManager.OnInstantiated += Network.Attach;
			if ((Object)(object)NetworkManager.Singleton != (Object)null)
			{
				Network.Attach(NetworkManager.Singleton);
			}
			LethalConfigNotes.Attach();
			GameObject val = new GameObject("ConfigSync notices");
			Object.DontDestroyOnLoad((Object)(object)val);
			((Object)val).hideFlags = (HideFlags)61;
			val.AddComponent<Notice>();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"ConfigSync 0.1.1 loaded");
		}
	}
	public enum Sync
	{
		HostRecommended,
		HostEnforced
	}
	public sealed class SyncedConfig
	{
		private static readonly Dictionary<string, SyncedConfig> all = new Dictionary<string, SyncedConfig>();

		internal readonly string Id;

		internal readonly string Name;

		internal readonly string Version;

		internal readonly Dictionary<string, ISyncedEntry> Entries = new Dictionary<string, ISyncedEntry>();

		private readonly string sidecarPath;

		internal bool Dirty;

		internal static IEnumerable<SyncedConfig> All => all.Values;

		internal static bool FollowRecommendations { get; set; } = true;

		internal static event Action<ISyncedEntry>? StateChanged;

		private SyncedConfig(string id, string name, string version, string configFilePath)
		{
			Id = id;
			Name = name;
			Version = version;
			sidecarPath = Path.ChangeExtension(configFilePath, ".sync.json");
		}

		public static SyncedConfig For(BaseUnityPlugin plugin)
		{
			BepInPlugin metadata = plugin.Info.Metadata;
			if (!all.TryGetValue(metadata.GUID, out SyncedConfig value))
			{
				value = new SyncedConfig(metadata.GUID, metadata.Name, metadata.Version.ToString(), plugin.Config.ConfigFilePath);
				all[metadata.GUID] = value;
				Network.Created(value);
			}
			return value;
		}

		public SyncedEntry<T> Share<T>(ConfigEntry<T> entry, Sync level = Sync.HostEnforced)
		{
			string key = Key(((ConfigEntryBase)entry).Definition);
			if (Entries.TryGetValue(key, out ISyncedEntry value) && value is SyncedEntry<T> syncedEntry && syncedEntry.Local == entry)
			{
				return syncedEntry;
			}
			SyncedEntry<T> synced = new SyncedEntry<T>(entry, level);
			Entries[key] = synced;
			entry.SettingChanged += delegate
			{
				synced.LocalChanged();
				Dirty = true;
			};
			WriteSidecar();
			Dirty = true;
			return synced;
		}

		internal static string Key(ConfigDefinition definition)
		{
			return definition.Section + "\n" + definition.Key;
		}

		internal static void RaiseStateChanged(ISyncedEntry entry)
		{
			SyncedConfig.StateChanged?.Invoke(entry);
		}

		internal static ISyncedEntry? Find(ConfigEntryBase entry)
		{
			return all.Values.SelectMany((SyncedConfig c) => c.Entries.Values).FirstOrDefault((ISyncedEntry e) => e.LocalEntry == entry);
		}

		internal static void ClearAll()
		{
			ForEachEntry(delegate(ISyncedEntry e)
			{
				e.Clear();
			});
		}

		internal static void RefreshAll()
		{
			ForEachEntry(delegate(ISyncedEntry e)
			{
				e.Refresh();
			});
		}

		private static void ForEachEntry(Action<ISyncedEntry> action)
		{
			foreach (SyncedConfig value in all.Values)
			{
				foreach (ISyncedEntry value2 in value.Entries.Values)
				{
					action(value2);
				}
			}
		}

		private void WriteSidecar()
		{
			StringBuilder stringBuilder = new StringBuilder("{\"version\":1,\"settings\":[");
			stringBuilder.Append(string.Join(",", Entries.Select<KeyValuePair<string, ISyncedEntry>, string>(delegate(KeyValuePair<string, ISyncedEntry> e)
			{
				string[] array = e.Key.Split('\n');
				string text2 = ((e.Value.Level == Sync.HostEnforced) ? "hostEnforced" : "hostRecommended");
				return "{\"section\":" + Quote(array[0]) + ",\"key\":" + Quote(array[1]) + ",\"level\":\"" + text2 + "\"}";
			})));
			stringBuilder.Append("]}\n");
			string text = stringBuilder.ToString();
			try
			{
				if (!File.Exists(sidecarPath) || File.ReadAllText(sidecarPath) != text)
				{
					File.WriteAllText(sidecarPath, text);
				}
			}
			catch (IOException ex)
			{
				ManualLogSource? source = Log.Source;
				if (source != null)
				{
					source.LogWarning((object)(Id + ": couldn't write " + sidecarPath + ": " + ex.Message));
				}
			}
		}

		private static string Quote(string value)
		{
			return string.Concat("\"", string.Concat(value.Select(delegate(char c)
			{
				if (c != '\\')
				{
					if (c != '"')
					{
						if (c >= ' ')
						{
							return c.ToString();
						}
						int num = c;
						return "\\u" + num.ToString("x4");
					}
					return "\\\"";
				}
				return "\\\\";
			})), "\"");
		}
	}
	internal interface ISyncedEntry
	{
		Sync Level { get; }

		ConfigEntryBase LocalEntry { get; }

		string? HostNote { get; }

		object? HostValue { get; }

		string? HostText { get; }

		string LocalText { get; }

		void Apply(string text);

		void Clear();

		void Refresh();
	}
	public sealed class SyncedEntry<T> : ISyncedEntry
	{
		private string? hostText;

		private T hostValue;

		public ConfigEntry<T> Local { get; }

		public Sync Level { get; }

		public bool FromHost { get; private set; }

		public T Value
		{
			get
			{
				if (!FromHost)
				{
					return Local.Value;
				}
				return hostValue;
			}
		}

		ConfigEntryBase ISyncedEntry.LocalEntry => (ConfigEntryBase)(object)Local;

		object? ISyncedEntry.HostValue
		{
			get
			{
				if (!FromHost)
				{
					return null;
				}
				return hostValue;
			}
		}

		string? ISyncedEntry.HostText => hostText;

		string? ISyncedEntry.HostNote
		{
			get
			{
				if (FromHost)
				{
					if (Level != Sync.HostEnforced)
					{
						return "You follow the host's recommendation of " + hostText + " while you're connected. Turn off \"Follow host recommendations\" in ConfigSync to use your own value.";
					}
					return "The host sets this to " + hostText + " while you're connected.";
				}
				return null;
			}
		}

		string ISyncedEntry.LocalText
		{
			get
			{
				if (!(Local.Value is string result))
				{
					return TomlTypeConverter.ConvertToString((object)Local.Value, typeof(T));
				}
				return result;
			}
		}

		public event Action? Changed;

		internal SyncedEntry(ConfigEntry<T> local, Sync level)
		{
			Local = local;
			Level = level;
		}

		void ISyncedEntry.Apply(string text)
		{
			T val = ((typeof(T) == typeof(string)) ? ((T)(object)text) : ((T)TomlTypeConverter.ConvertToValue(text, typeof(T))));
			AcceptableValueBase acceptableValues = ((ConfigEntryBase)Local).Description.AcceptableValues;
			if (acceptableValues != null)
			{
				val = (T)acceptableValues.Clamp((object)val);
			}
			T value = Value;
			hostText = text;
			hostValue = val;
			Update(value);
		}

		void ISyncedEntry.Clear()
		{
			T value = Value;
			hostText = null;
			Update(value);
		}

		void ISyncedEntry.Refresh()
		{
			Update(Value);
		}

		internal void LocalChanged()
		{
			if (!FromHost)
			{
				RaiseChanged();
			}
		}

		private void Update(T before)
		{
			bool fromHost = FromHost;
			FromHost = hostText != null && (Level == Sync.HostEnforced || SyncedConfig.FollowRecommendations);
			if (!EqualityComparer<T>.Default.Equals(before, Value))
			{
				RaiseChanged();
			}
			if (fromHost || FromHost)
			{
				SyncedConfig.RaiseStateChanged(this);
			}
		}

		private void RaiseChanged()
		{
			try
			{
				this.Changed?.Invoke();
			}
			catch (Exception arg)
			{
				ManualLogSource? source = Log.Source;
				if (source != null)
				{
					source.LogError((object)$"{((ConfigEntryBase)Local).Definition}: a Changed handler threw: {arg}");
				}
			}
		}
	}
	internal static class MyPluginInfo
	{
		public const string Id = "tech.rethunk.mortar.ConfigSync";

		public const string Name = "ConfigSync";

		public const string Version = "0.1.1";
	}
}