Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ConfigSync v0.1.1
plugins/tech.rethunk.mortar.ConfigSync.dll
Decompiled 15 hours agousing 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"; } }