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 LethalConfigSync v0.0.2
plugins/bobabulkerENTERPRISE.LethalConfigSync.dll
Decompiled 18 hours agousing 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); } } }