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 ConditionalConfigSync v1.0.0
ConditionalConfigSync.dll
Decompiled 19 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("Conditional Config Sync")] [assembly: AssemblyDescription("Shared config synchronization and server policy library for Valheim mods.")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("shudnal")] [assembly: AssemblyProduct("Conditional Config Sync")] [assembly: AssemblyCopyright("Copyright © shudnal 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("566458c7-717b-4583-af37-a69e4cd02966")] [assembly: AssemblyFileVersion("1.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/shudnal/ConditionalConfigSync")] [assembly: AssemblyMetadata("License", "Unlicense")] [assembly: InternalsVisibleTo("ConditionalConfigSync.Plugin")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.0.0.0")] [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 ConditionalConfigSync { [Description("Registers and synchronizes config entries and runtime values for one mod.")] public class ConditionalConfigSync { internal static class ConfigEntryGetSerializedValuePatch { internal static bool Prefix(ConfigEntryBase __instance, ref string __result) { OwnConfigEntryBase configData = GetConfigData(__instance); if (configData == null || IsWritableConfig(configData)) { return true; } __result = TomlTypeConverter.ConvertToString(configData.LocalBaseValue, __instance.SettingType); return false; } } internal static class ConfigEntrySetSerializedValuePatch { internal static bool Prefix(ConfigEntryBase __instance, string value) { OwnConfigEntryBase configData = GetConfigData(__instance); if (configData == null || !configData.HasLocalBaseValue) { return true; } try { configData.StoreLocalBaseValue(TomlTypeConverter.ConvertToValue(value, __instance.SettingType)); } catch (Exception ex) { LogSource.LogWarning((object)$"[ConfigFile] Config value of setting \"{__instance.Definition}\" could not be parsed and will be ignored. Reason: {ex.Message}"); } return false; } } [Description("Diagnostic logging verbosity for ConditionalConfigSync.")] public enum ConditionalConfigSyncDebugLevel { Basic = 1, Verbose, Trace } private readonly struct ReceivedConfigState { public readonly bool ServerControlled; public readonly bool Hidden; public ReceivedConfigState(bool serverControlled, bool hidden) { ServerControlled = serverControlled; Hidden = hidden; } } private class ParsedConfigs { public readonly Dictionary<OwnConfigEntryBase, object?> configValues = new Dictionary<OwnConfigEntryBase, object>(); public readonly Dictionary<CustomSyncedValueBase, object?> customValues = new Dictionary<CustomSyncedValueBase, object>(); public readonly Dictionary<OwnConfigEntryBase, ReceivedConfigState> configStates = new Dictionary<OwnConfigEntryBase, ReceivedConfigState>(); } private enum PackageEntryKind : byte { Config = 1, CustomValue, ServerVersion, LockExempt, ConfigState } private class PackageEntry { public PackageEntryKind kind; public string? section; public string? key; public Type? type; public object? value; public bool serverControlled; public bool hidden; public static PackageEntry ServerVersion(string version) { return new PackageEntry { kind = PackageEntryKind.ServerVersion, value = version }; } public static PackageEntry LockExempt(bool value) { return new PackageEntry { kind = PackageEntryKind.LockExempt, value = value }; } public static PackageEntry ConfigState(ConfigEntryBase config, bool serverControlled, bool hidden) { return new PackageEntry { kind = PackageEntryKind.ConfigState, section = config.Definition.Section, key = config.Definition.Key, serverControlled = serverControlled, hidden = hidden }; } } private class InvalidDeserializationTypeException : Exception { public string expected = null; public string received = null; public string field = ""; } private enum ConfigPolicyOverride { Default, ForceServerControlled, ForceClientControlled } private sealed class SyncPolicyRecord { internal readonly string DisplayText; internal readonly string? Key; internal readonly ConfigPolicyOverride PolicyOverride; internal readonly string? Error; internal SyncPolicyRecord(string displayText, string? key, ConfigPolicyOverride policyOverride, string? error) { DisplayText = displayText; Key = key; PolicyOverride = policyOverride; Error = error; } } private sealed class HiddenPolicyRecord { internal readonly string DisplayText; internal readonly string? Key; internal readonly string? Error; internal HiddenPolicyRecord(string displayText, string? key, string? error) { DisplayText = displayText; Key = key; Error = error; } } private enum PolicyTargetFailure { None, Mod, Section, Config } private sealed class PolicyTargetResolution { internal ConditionalConfigSync? ConfigSync; internal readonly List<OwnConfigEntryBase> Configs = new List<OwnConfigEntryBase>(); internal PolicyTargetFailure Failure; } internal static class TerminalInitPatch { internal static void Postfix() { RegisterDebugConsoleCommands(); } } internal static class ZNetUpdatePatch { internal static void Postfix() { DrainMainThreadQueue(); } } internal static class ZNetAwakePatch { internal static void Postfix(ZNet __instance) { EnsureDebugSupportInitialized(); isServer = GameReflection.IsServer(__instance); sessionActive = true; if (isServer) { EnsurePolicySupportInitialized(createIfMissing: true); } foreach (ConditionalConfigSync configSync in configSyncs) { configSync.RegisterForActiveSession(); } if (isServer) { GameReflection.StartCoroutine(WatchAdminListChanges(), __instance); } void SendAdmin(List<ZNetPeer> peers, bool isAdmin) { ZPackage package = ConfigsToPackage(null, null, new PackageEntry[1] { PackageEntry.LockExempt(isAdmin) }); ConditionalConfigSync conditionalConfigSync = configSyncs.FirstOrDefault(); if (conditionalConfigSync != null) { GameReflection.StartCoroutine(conditionalConfigSync.SendZPackage(peers, package), __instance); } } IEnumerator WatchAdminListChanges() { SyncedList adminList = GameReflection.GetAdminList(__instance) ?? throw new InvalidOperationException("ZNet admin list is unavailable."); List<string> currentList = GameReflection.GetSyncedListValues(adminList); while (true) { yield return (object)new WaitForSeconds(30f); if (!GameReflection.GetSyncedListValues(adminList).SequenceEqual(currentList)) { currentList = GameReflection.GetSyncedListValues(adminList); List<ZNetPeer> peers = GameReflection.GetPeers(__instance); List<ZNetPeer> adminPeer = peers.Where(IsPeerAdmin).ToList(); List<ZNetPeer> nonAdminPeer = peers.Except(adminPeer).ToList(); foreach (ConditionalConfigSync sync in configSyncs) { sync.DebugLog(ConditionalConfigSyncDebugLevel.Basic, "Admin", $"Admin list changed, admins={adminPeer.Count}, nonAdmins={nonAdminPeer.Count}"); } SendAdmin(nonAdminPeer, isAdmin: false); SendAdmin(adminPeer, isAdmin: true); } } } } } internal static class ZNetOnNewConnectionPatch { internal static void Postfix(ZNet __instance, ZNetPeer peer) { if (GameReflection.IsServer(__instance)) { return; } foreach (ConditionalConfigSync configSync in configSyncs) { configSync.RegisterClientRpcHandler(peer); } } } internal static class ZNetShutdownPatch { internal static void Postfix() { ConditionalConfigSync[] array = configSyncs.ToArray(); ProcessingServerUpdate = true; lockExempt = false; try { ConditionalConfigSync[] array2 = array; foreach (ConditionalConfigSync conditionalConfigSync in array2) { try { conditionalConfigSync.DebugLog(ConditionalConfigSyncDebugLevel.Basic, "Shutdown", "Reset local values and source-of-truth state"); conditionalConfigSync.ResetConfigsFromServer(); conditionalConfigSync.IsSourceOfTruth = true; conditionalConfigSync.InitialSyncDone = false; conditionalConfigSync.pendingConfigBroadcasts.Clear(); conditionalConfigSync.pendingCustomValueBroadcasts.Clear(); conditionalConfigSync.pendingSequencedCustomValuePackages.Clear(); string[] array3 = conditionalConfigSync.configValueCache.Keys.ToArray(); foreach (string cacheKey in array3) { conditionalConfigSync.RemoveFragmentAssembly(cacheKey); } } catch (Exception arg) { conditionalConfigSync.DebugWarning("Shutdown", $"Failed to reset one synchronization instance; continuing. Error: {arg}"); } } } finally { ProcessingServerUpdate = false; } ConditionalConfigSync[] array4 = array; foreach (ConditionalConfigSync conditionalConfigSync2 in array4) { conditionalConfigSync2.InvokeEventHandlers(conditionalConfigSync2.ServerConnectionReset, "ServerConnectionReset"); } ResetNetworkSessionState(); isServer = false; } } internal static class ZNetRpcPeerInfoSyncPatch { internal class BufferingSocket : ZPlayFabSocket, ISocket { public volatile bool finished = false; public volatile int versionMatchQueued = -1; public readonly List<ZPackage> Package = new List<ZPackage>(); public readonly ISocket Original; internal BufferingSocket(ISocket original) { Original = original; } public bool IsConnected() { return GameReflection.SocketIsConnected(Original); } public ZPackage Recv() { return GameReflection.SocketRecv(Original); } public int GetSendQueueSize() { return GameReflection.SocketGetSendQueueSize(Original); } public int GetCurrentSendRate() { return GameReflection.SocketGetCurrentSendRate(Original); } public bool IsHost() { return GameReflection.SocketIsHost(Original); } public void Dispose() { GameReflection.SocketDispose(Original); } public bool GotNewData() { return GameReflection.SocketGotNewData(Original); } public void Close() { GameReflection.SocketClose(Original); } public string GetEndPointString() { return GameReflection.SocketGetEndPointString(Original); } public void GetAndResetStats(out int totalSent, out int totalRecv) { GameReflection.SocketGetAndResetStats(Original, out totalSent, out totalRecv); } public void GetConnectionQuality(out float localQuality, out float remoteQuality, out int ping, out float outByteSec, out float inByteSec) { GameReflection.SocketGetConnectionQuality(Original, out localQuality, out remoteQuality, out ping, out outByteSec, out inByteSec); } public ISocket Accept() { return GameReflection.SocketAccept(Original); } public int GetHostPort() { return GameReflection.SocketGetHostPort(Original); } public bool Flush() { return GameReflection.SocketFlush(Original); } public string GetHostName() { return GameReflection.SocketGetHostName(Original); } public void VersionMatch() { if (finished) { GameReflection.SocketVersionMatch(Original); } else { versionMatchQueued = Package.Count; } } public void Send(ZPackage pkg) { int position = GameReflection.PackageGetPos(pkg); GameReflection.PackageSetPos(pkg, 0); int num = GameReflection.PackageReadInt(pkg); if ((num == GameReflection.StableHash("PeerInfo") || num == GameReflection.StableHash("RoutedRPC") || num == GameReflection.StableHash("ZDOData")) && !finished) { ZPackage val = GameReflection.NewPackage(GameReflection.PackageGetArray(pkg)); GameReflection.PackageSetPos(val, position); Package.Add(val); } else { GameReflection.PackageSetPos(pkg, position); GameReflection.SocketSend(Original, pkg); } } } internal static void Prefix(ref BufferingSocket? __state, ZNet __instance, ZRpc rpc) { if (!GameReflection.IsServer(__instance)) { return; } BufferingSocket bufferingSocket = new BufferingSocket(GameReflection.GetRpcSocket(rpc) ?? throw new InvalidOperationException("RPC socket is unavailable.")); GameReflection.SetRpcSocket(rpc, (ISocket)(object)bufferingSocket); ZNetPeer peer = GameReflection.GetPeer(rpc, __instance); if (peer != null && !object.Equals(GameReflection.GetOnlineBackend(), (object)(OnlineBackendType)0)) { ISocket? peerSocket = GameReflection.GetPeerSocket(peer); ZPlayFabSocket val = (ZPlayFabSocket)(object)((peerSocket is ZPlayFabSocket) ? peerSocket : null); if (val != null) { GameReflection.SetPlayFabRemotePlayerId((ZPlayFabSocket)(object)bufferingSocket, GameReflection.GetPlayFabRemotePlayerId(val)); } GameReflection.SetPeerSocket(peer, (ISocket)(object)bufferingSocket); } __state = bufferingSocket; } internal static void Postfix(BufferingSocket? __state, ZNet __instance, ZRpc rpc) { ZNetPeer peer; if (GameReflection.IsServer(__instance) && __state != null) { peer = GameReflection.GetPeer(rpc, __instance); if (peer == null) { SendBufferedData(); } else { GameReflection.StartCoroutine(sendAsync(), __instance); } } void SendBufferedData() { BufferingSocket bufferingSocket2; if (GameReflection.GetRpcSocket(rpc) is BufferingSocket bufferingSocket) { GameReflection.SetRpcSocket(rpc, bufferingSocket.Original); ZNetPeer peer2 = GameReflection.GetPeer(rpc, __instance); if (peer2 != null) { GameReflection.SetPeerSocket(peer2, bufferingSocket.Original); } bufferingSocket2 = bufferingSocket; } else { bufferingSocket2 = __state; } if (!bufferingSocket2.finished) { bufferingSocket2.finished = true; for (int i = 0; i < bufferingSocket2.Package.Count; i++) { if (i == bufferingSocket2.versionMatchQueued) { GameReflection.SocketVersionMatch(bufferingSocket2.Original); } GameReflection.SocketSend(bufferingSocket2.Original, bufferingSocket2.Package[i]); } if (bufferingSocket2.Package.Count == bufferingSocket2.versionMatchQueued) { GameReflection.SocketVersionMatch(bufferingSocket2.Original); } } } IEnumerator sendAsync() { try { foreach (ConditionalConfigSync configSync in configSyncs) { ZPackage package = configSync.CreateFullSyncPackage(peer); configSync.DebugLog(ConditionalConfigSyncDebugLevel.Basic, "InitialSync", $"Sending full sync to {FormatPeer(peer)}, admin={IsPeerAdmin(peer)}, configs={configSync.allConfigs.Count}, custom={configSync.allCustomValues.Count}, size={GameReflection.PackageSize(package)}"); yield return GameReflection.StartCoroutine(configSync.SendZPackage(new List<ZNetPeer> { peer }, package), __instance); } } finally { SendBufferedData(); } } } } [Description("True while an incoming synchronization package is being applied. Usually diagnostic only.")] public static bool ProcessingServerUpdate = false; [Description("Stable unique synchronization identifier, normally the owning mod GUID.")] public readonly string Name; [Description("Human-readable mod name used in logs.")] public string? DisplayName; [Description("Current version of the owning mod used for peer compatibility checks.")] public string? CurrentVersion; [Description("Oldest compatible remote mod version accepted by this instance.")] public string? MinimumRequiredVersion; [Description("Allows unlocked clients to publish changes, matching ServerSync unlocked-client behavior.")] public bool AllowClientConfigUpdatesWhenUnlocked = true; private bool? forceConfigLocking; private bool isSourceOfTruth = true; private static readonly HashSet<ConditionalConfigSync> configSyncs = new HashSet<ConditionalConfigSync>(); private readonly HashSet<OwnConfigEntryBase> allConfigs = new HashSet<OwnConfigEntryBase>(); private HashSet<CustomSyncedValueBase> allCustomValues = new HashSet<CustomSyncedValueBase>(); private static bool isServer; private static bool lockExempt = false; private OwnConfigEntryBase? lockedConfig = null; private bool? lastNotifiedLockState; [Description("Enables shared ConditionalConfigSync diagnostic logging programmatically.")] public static bool DebugLoggingEnabled = false; [Description("Selects diagnostic logging verbosity.")] public static ConditionalConfigSyncDebugLevel DebugLoggingLevel = ConditionalConfigSyncDebugLevel.Basic; [Description("Optional case-insensitive filter matched against mod sync names and display names.")] public static string DebugLoggingFilter = ""; private static bool debugConfigEnabled; private static ConditionalConfigSyncDebugLevel debugConfigLevel = ConditionalConfigSyncDebugLevel.Basic; private static string debugConfigFilter = ""; private static bool debugSupportInitialized; private static bool debugConfigReloadScheduled; private static bool debugCommandsRegistered; private static FileSystemWatcher? debugConfigWatcher; private static readonly object debugSupportLock = new object(); private const string SyncPolicyFileName = "ConditionalConfigSync.SyncPolicy.cfg"; private const string HiddenConfigsFileName = "ConditionalConfigSync.HiddenConfigs.cfg"; private const string PolicyDumpFileName = "ConditionalConfigSync.PolicyDump.txt"; private static readonly object policyLock = new object(); private static bool policySupportInitialized; private static bool policyReloadScheduled; private static long policyReadGeneration; private static long policyAppliedGeneration; private static FileSystemWatcher? syncPolicyWatcher; private static FileSystemWatcher? hiddenConfigsWatcher; private static Dictionary<string, ConfigPolicyOverride> syncPolicy = new Dictionary<string, ConfigPolicyOverride>(StringComparer.OrdinalIgnoreCase); private static HashSet<string> hiddenConfigPolicy = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static ManualLogSource? logSource; private static readonly object runtimeLock = new object(); private static bool runtimeInitialized; private static int mainThreadId; private static readonly object mainThreadQueueLock = new object(); private static readonly Queue<Action> mainThreadQueue = new Queue<Action>(); private const string ConfigDirectoryName = "shudnal.ConditionalConfigSync"; private const string DebugConfigFileName = "ConditionalConfigSync.Debug.cfg"; private static readonly object configDirectoryLock = new object(); private static bool configDirectoryInitialized; private const string ResyncRpcSuffix = " ConditionalConfigSync Resync"; private const int StableReadAttempts = 5; private const int StableReadDelayMilliseconds = 50; private readonly VersionCheck versionCheck; private bool serverRpcsRegistered; private readonly HashSet<ZRpc> registeredClientRpcs = new HashSet<ZRpc>(); private readonly HashSet<OwnConfigEntryBase> lateRegisteredConfigs = new HashSet<OwnConfigEntryBase>(); private readonly HashSet<CustomSyncedValueBase> lateRegisteredCustomValues = new HashSet<CustomSyncedValueBase>(); private bool lateRegistrationSyncScheduled; private static bool sessionActive; private static Harmony? runtimeHarmony; private static AssemblyLoadEventHandler? assemblyLoadHandler; private short sendCount; private short processingCount; private bool lastHandledPackageWasFull; private readonly HashSet<ConfigEntryBase> pendingConfigBroadcasts = new HashSet<ConfigEntryBase>(); private readonly HashSet<CustomSyncedValueBase> pendingCustomValueBroadcasts = new HashSet<CustomSyncedValueBase>(); private readonly Queue<ZPackage> pendingSequencedCustomValuePackages = new Queue<ZPackage>(); private bool flushingPendingBroadcasts; private readonly HashSet<ConfigEntryBase> configsBeingApplied = new HashSet<ConfigEntryBase>(); private readonly HashSet<CustomSyncedValueBase> customValuesBeingApplied = new HashSet<CustomSyncedValueBase>(); private const byte PARTIAL_CONFIGS = 1; private const byte FRAGMENTED_CONFIG = 2; private const byte COMPRESSED_CONFIG = 4; private const byte V2_PACKAGE = 8; private const int packageSliceSize = 250000; private const int maximumSendQueueSize = 20000; private const int compressMinSize = 10000; private const int maxPackageEntries = 8192; private const int maxPayloadSize = 20971520; private const int maxFragments = 128; private const int maxFragmentSize = 300000; private const int maxFragmentAssembliesPerSender = 4; private const int maxFragmentCacheBytesPerSender = 20971520; private const int maxFragmentCacheBytesGlobal = 67108864; private const int maxPendingSequencedUpdates = 100; private readonly Dictionary<string, SortedDictionary<int, byte[]>> configValueCache = new Dictionary<string, SortedDictionary<int, byte[]>>(); private readonly Dictionary<string, int> configValueCacheExpectedFragments = new Dictionary<string, int>(); private readonly Dictionary<string, int> configValueCacheBytes = new Dictionary<string, int>(); private readonly Dictionary<string, long> configValueCacheSenders = new Dictionary<string, long>(); private readonly List<KeyValuePair<long, string>> cacheExpirations = new List<KeyValuePair<long, string>>(); private static long packageCounter = 0L; [Description("Whether the owning mod must be installed and compatible on the remote peer.")] public bool ModRequired { get; set; } = false; [Description("Whether non-admin clients are currently blocked from publishing server-controlled settings.")] public bool IsLocked { get { bool? flag = forceConfigLocking; bool num; if (!flag.HasValue) { if (lockedConfig == null) { goto IL_0052; } num = ((IConvertible)lockedConfig.BaseConfig.BoxedValue).ToInt32(CultureInfo.InvariantCulture) != 0; } else { num = flag == true; } if (!num) { goto IL_0052; } int result = ((!lockExempt) ? 1 : 0); goto IL_0053; IL_0052: result = 0; goto IL_0053; IL_0053: return (byte)result != 0; } set { bool isLocked = IsLocked; forceConfigLocking = value; if (isLocked != IsLocked) { ServerLockedSettingChanged(); } } } [Description("Whether the local side is the source of truth or has a server admin exemption.")] public bool IsAdmin => lockExempt || isSourceOfTruth; [Description("Whether the local side currently owns and publishes synchronized values.")] public bool IsSourceOfTruth { get { return isSourceOfTruth; } private set { if (value != isSourceOfTruth) { isSourceOfTruth = value; InvokeEventHandlers(this.SourceOfTruthChanged, value, "SourceOfTruthChanged"); } } } [Description("Whether initial server synchronization has completed for this mod instance.")] public bool InitialSyncDone { get; private set; } = false; private static bool IsDebugActive => DebugLoggingEnabled || debugConfigEnabled; private static ConditionalConfigSyncDebugLevel EffectiveDebugLevel { get { int num = 0; if (DebugLoggingEnabled) { num = Math.Max(num, (int)DebugLoggingLevel); } if (debugConfigEnabled) { num = Math.Max(num, (int)debugConfigLevel); } return (num <= 0) ? ConditionalConfigSyncDebugLevel.Basic : ((ConditionalConfigSyncDebugLevel)num); } } private static string EffectiveDebugFilter => (!string.IsNullOrWhiteSpace(DebugLoggingFilter)) ? DebugLoggingFilter : debugConfigFilter; private static string SyncPolicyPath => Path.Combine(ConfigDirectoryPath, "ConditionalConfigSync.SyncPolicy.cfg"); private static string HiddenConfigsPath => Path.Combine(ConfigDirectoryPath, "ConditionalConfigSync.HiddenConfigs.cfg"); private static ManualLogSource LogSource => logSource ?? (logSource = Logger.CreateLogSource("ConditionalConfigSync")); private static string ConfigDirectoryPath => Path.Combine(Paths.ConfigPath, "shudnal.ConditionalConfigSync"); private static string DebugConfigPath => Path.Combine(ConfigDirectoryPath, "ConditionalConfigSync.Debug.cfg"); private static bool IsMainThread => mainThreadId == 0 || Thread.CurrentThread.ManagedThreadId == mainThreadId; private bool IsSending => sendCount > 0; private bool IsProcessing => processingCount > 0; private bool ShouldDeferOutgoingBroadcasts => ProcessingServerUpdate || IsProcessing || IsSending || flushingPendingBroadcasts; [Description("Raised when this process changes between authoritative and client-replica roles.")] public event Action<bool>? SourceOfTruthChanged; [Description("Raised after the first complete server synchronization has been applied on a client.")] public event Action? InitialSyncCompleted; [Description("Raised after server state is cleared and local fallback values are restored.")] public event Action? ServerConnectionReset; [Description("Raised for any effective policy-state transition of a registered config entry.")] public event EventHandler<PolicyStateChangedEventArgs>? PolicyStateChanged; [Description("Raised when a config entry changes between server-controlled and client-controlled.")] public event Action<OwnConfigEntryBase, bool>? ServerControlledChanged; [Description("Raised when a config entry changes between hidden and visible.")] public event Action<OwnConfigEntryBase, bool>? HiddenStateChanged; [Description("Raised when the effective configuration lock changes.")] public event Action<bool>? LockStateChanged; [Description("Raised when synchronization is rejected for permissions, safety limits, malformed data, or an exception.")] public event EventHandler<SyncRejectedEventArgs>? SyncRejected; private event Action? LockedConfigChanged; [Description("Creates one synchronization instance. Pass a stable mod GUID as the name.")] public ConditionalConfigSync(string name) { EnsureRuntimeReady(); Name = name; configSyncs.Add(this); versionCheck = new VersionCheck(this); if (sessionActive) { QueueMainThread(RegisterForActiveSession); } } [Description("Registers a policy-controlled config that is server-controlled by default.")] public SyncedConfigEntry<T> AddConfigEntry<T>(ConfigEntry<T> configEntry) { return AddConfigEntry<T>(configEntry, ConfigSyncMode.Conditional, serverControlledByDefault: true); } [Description("Registers a Conditional config with an explicit server/client default.")] public SyncedConfigEntry<T> AddConfigEntry<T>(ConfigEntry<T> configEntry, bool synchronizedSetting) { return AddConfigEntry<T>(configEntry, ConfigSyncMode.Conditional, synchronizedSetting); } [Description("Registers a config with an explicit synchronization mode.")] public SyncedConfigEntry<T> AddConfigEntry<T>(ConfigEntry<T> configEntry, ConfigSyncMode syncMode) { return AddConfigEntry<T>(configEntry, syncMode, serverControlledByDefault: true); } [Description("Registers a fully configured config without a temporary synchronization state.")] public SyncedConfigEntry<T> AddConfigEntry<T>(ConfigEntry<T> configEntry, ConfigSyncMode syncMode, bool serverControlledByDefault) { if (!Enum.IsDefined(typeof(ConfigSyncMode), syncMode)) { throw new ArgumentOutOfRangeException("syncMode", syncMode, "Unknown config synchronization mode."); } bool flag = syncMode switch { ConfigSyncMode.AlwaysServerControlled => true, ConfigSyncMode.AlwaysClientControlled => false, _ => serverControlledByDefault, }; if (GetConfigData((ConfigEntryBase)(object)configEntry) is SyncedConfigEntry<T> result) { return result; } ConfigDefinition definition = ((ConfigEntryBase)configEntry).Definition; if (allConfigs.Any((OwnConfigEntryBase config) => config.BaseConfig.Definition.Equals(definition))) { throw new InvalidOperationException("Config entry '" + definition.Section + " -> " + definition.Key + "' is already registered in sync '" + Name + "'."); } SyncedConfigEntry<T> syncedEntry = new SyncedConfigEntry<T>(configEntry) { SyncMode = syncMode, SynchronizedConfig = flag }; AccessTools.DeclaredField(typeof(ConfigDescription), "<Tags>k__BackingField").SetValue(((ConfigEntryBase)configEntry).Description, new object[1] { new ConfigurationManagerAttributes() }.Concat(((ConfigEntryBase)configEntry).Description.Tags ?? Array.Empty<object>()).Concat(new SyncedConfigEntry<T>[1] { syncedEntry }).ToArray()); configEntry.SettingChanged += delegate { OnConfigEntryChanged((ConfigEntryBase)(object)configEntry, syncedEntry); }; allConfigs.Add(syncedEntry); bool flag2 = IsSourceOfTruth && isServer && GameReflection.HasZNet && policySupportInitialized; syncedEntry.IsServerControlled = (flag2 ? ComputeServerControlled(syncedEntry) : GetDefaultServerControlled(syncedEntry)); syncedEntry.IsHidden = flag2 && ComputeHidden(syncedEntry); syncedEntry.PolicyStateInitialized = flag2; ConfigurationManagerAttributes configAttribute = GetConfigAttribute<ConfigurationManagerAttributes>((ConfigEntryBase)(object)configEntry); configAttribute.ReadOnly = !IsWritableConfig(syncedEntry); configAttribute.Browsable = !syncedEntry.IsHidden; DebugLog(ConditionalConfigSyncDebugLevel.Trace, "Register", string.Format("Added config {0}/{1}, type={2}, mode={3}, default={4}", definition.Section, definition.Key, ((ConfigEntryBase)configEntry).SettingType.Name, syncMode, flag ? "ServerControlled" : "ClientControlled")); ScheduleLateRegistrationSync(syncedEntry); return syncedEntry; } [Description("Binds and registers a Conditional config with an explicit server/client default.")] public SyncedConfigEntry<T> AddConfigEntry<T>(ConfigFile configFile, string section, string key, T defaultValue, string description, bool synchronizedSetting) { return AddConfigEntry<T>(configFile.Bind<T>(section, key, defaultValue, description), ConfigSyncMode.Conditional, synchronizedSetting); } [Description("Binds and registers a config in one call using a text description.")] public SyncedConfigEntry<T> AddConfigEntry<T>(ConfigFile configFile, string section, string key, T defaultValue, string description, ConfigSyncMode syncMode = ConfigSyncMode.Conditional, bool serverControlledByDefault = true) { return AddConfigEntry<T>(configFile.Bind<T>(section, key, defaultValue, description), syncMode, serverControlledByDefault); } [Description("Binds and registers a Conditional ConfigDescription entry with an explicit server/client default.")] public SyncedConfigEntry<T> AddConfigEntry<T>(ConfigFile configFile, string section, string key, T defaultValue, ConfigDescription description, bool synchronizedSetting) { return AddConfigEntry<T>(configFile.Bind<T>(section, key, defaultValue, description), ConfigSyncMode.Conditional, synchronizedSetting); } [Description("Binds and registers a config in one call using a ConfigDescription.")] public SyncedConfigEntry<T> AddConfigEntry<T>(ConfigFile configFile, string section, string key, T defaultValue, ConfigDescription description, ConfigSyncMode syncMode = ConfigSyncMode.Conditional, bool serverControlledByDefault = true) { return AddConfigEntry<T>(configFile.Bind<T>(section, key, defaultValue, description), syncMode, serverControlledByDefault); } [Description("Binds and registers a Conditional ConfigDefinition entry with a text description and explicit default.")] public SyncedConfigEntry<T> AddConfigEntry<T>(ConfigFile configFile, ConfigDefinition definition, T defaultValue, string description, bool synchronizedSetting) { return AddConfigEntry<T>(configFile.Bind<T>(definition.Section, definition.Key, defaultValue, description), ConfigSyncMode.Conditional, synchronizedSetting); } [Description("Binds and registers a ConfigDefinition entry with a text description.")] public SyncedConfigEntry<T> AddConfigEntry<T>(ConfigFile configFile, ConfigDefinition definition, T defaultValue, string description, ConfigSyncMode syncMode = ConfigSyncMode.Conditional, bool serverControlledByDefault = true) { return AddConfigEntry<T>(configFile.Bind<T>(definition.Section, definition.Key, defaultValue, description), syncMode, serverControlledByDefault); } [Description("Binds and registers a Conditional ConfigDefinition entry with an explicit server/client default.")] public SyncedConfigEntry<T> AddConfigEntry<T>(ConfigFile configFile, ConfigDefinition definition, T defaultValue, ConfigDescription description, bool synchronizedSetting) { return AddConfigEntry<T>(configFile.Bind<T>(definition.Section, definition.Key, defaultValue, description), ConfigSyncMode.Conditional, synchronizedSetting); } [Description("Binds and registers a config in one call using a ConfigDefinition.")] public SyncedConfigEntry<T> AddConfigEntry<T>(ConfigFile configFile, ConfigDefinition definition, T defaultValue, ConfigDescription description, ConfigSyncMode syncMode = ConfigSyncMode.Conditional, bool serverControlledByDefault = true) { return AddConfigEntry<T>(configFile.Bind<T>(definition.Section, definition.Key, defaultValue, description), syncMode, serverControlledByDefault); } [Description("Registers the protected server-controlled lock entry. Non-admin clients can never change it.")] public SyncedConfigEntry<T> AddLockingConfigEntry<T>(ConfigEntry<T> lockingConfig) where T : IConvertible { if (lockedConfig != null) { throw new Exception("Cannot initialize locking ConfigEntry twice"); } lockedConfig = AddConfigEntry<T>(lockingConfig, ConfigSyncMode.AlwaysServerControlled); lockedConfig.IsServerControlled = true; lockedConfig.PolicyStateInitialized = false; lockingConfig.SettingChanged += delegate { this.LockedConfigChanged?.Invoke(); }; LockedConfigChanged -= ServerLockedSettingChanged; LockedConfigChanged += ServerLockedSettingChanged; return (SyncedConfigEntry<T>)lockedConfig; } internal void AddCustomValue(CustomSyncedValueBase customValue) { if (allCustomValues.Select((CustomSyncedValueBase v) => v.Identifier).Concat(new string[2] { "serverversion", "lockexempt" }).Contains(customValue.Identifier)) { throw new Exception("Cannot have multiple settings with the same name or with a reserved name (serverversion, lockexempt)"); } allCustomValues.Add(customValue); allCustomValues = new HashSet<CustomSyncedValueBase>(from v in allCustomValues orderby v.Priority descending, v.RegistrationIndex select v); customValue.ValueChanged += delegate { OnCustomValueChanged(customValue); }; DebugLog(ConditionalConfigSyncDebugLevel.Trace, "Register", $"Added custom value {customValue.Identifier}, type={customValue.Type.Name}, priority={customValue.Priority}, sequenced={customValue.PreserveUpdateSequence}"); ScheduleLateRegistrationSync(null, customValue); } private static bool IsWritableConfig(OwnConfigEntryBase config) { ConditionalConfigSync conditionalConfigSync = configSyncs.FirstOrDefault((ConditionalConfigSync cs) => cs.allConfigs.Contains(config)); if (conditionalConfigSync == null) { return true; } return conditionalConfigSync.IsSourceOfTruth || !config.IsServerControlled || !config.HasLocalBaseValue || (!conditionalConfigSync.IsLocked && (config != conditionalConfigSync.lockedConfig || lockExempt)); } private void ServerLockedSettingChanged() { RaiseLockStateChangedIfNeeded(); DebugLog(ConditionalConfigSyncDebugLevel.Verbose, "Lock", $"Re-evaluating read-only state: locked={IsLocked}, admin={IsAdmin}, sourceOfTruth={IsSourceOfTruth}"); foreach (OwnConfigEntryBase allConfig in allConfigs) { ConfigurationManagerAttributes configAttribute = GetConfigAttribute<ConfigurationManagerAttributes>(allConfig.BaseConfig); configAttribute.ReadOnly = !IsWritableConfig(allConfig); configAttribute.Browsable = !allConfig.IsHidden; } } private void ResetConfigsFromServer() { DebugLog(ConditionalConfigSyncDebugLevel.Verbose, "Reset", "Restoring local values after server sync/session end"); Dictionary<ConfigFile, bool> dictionary = new Dictionary<ConfigFile, bool>(); List<PolicyStateChangedEventArgs> list = new List<PolicyStateChangedEventArgs>(); try { foreach (OwnConfigEntryBase item in allConfigs.Where((OwnConfigEntryBase config) => config.HasLocalBaseValue)) { ConfigFile configFile = item.BaseConfig.ConfigFile; if (!dictionary.ContainsKey(configFile)) { dictionary[configFile] = configFile.SaveOnConfigSet; configFile.SaveOnConfigSet = false; } configsBeingApplied.Add(item.BaseConfig); try { item.BaseConfig.BoxedValue = item.LocalBaseValue; item.ClearLocalBaseValue(); item.ClearServerValue(); } catch (Exception arg) { ConfigDefinition definition = item.BaseConfig.Definition; DebugWarning("Reset", $"Failed to restore local config {definition.Section} -> {definition.Key}; continuing. Error: {arg}"); } finally { configsBeingApplied.Remove(item.BaseConfig); } } } finally { foreach (KeyValuePair<ConfigFile, bool> item2 in dictionary) { item2.Key.SaveOnConfigSet = item2.Value; } } foreach (OwnConfigEntryBase allConfig in allConfigs) { bool isServerControlled = allConfig.IsServerControlled; bool isHidden = allConfig.IsHidden; bool defaultServerControlled = GetDefaultServerControlled(allConfig); allConfig.ClearServerValue(); allConfig.IsServerControlled = defaultServerControlled; allConfig.IsHidden = false; allConfig.PolicyStateInitialized = false; if (isServerControlled != defaultServerControlled || isHidden) { list.Add(new PolicyStateChangedEventArgs(allConfig, isServerControlled, defaultServerControlled, isHidden, newHidden: false, "server connection reset")); } } foreach (CustomSyncedValueBase item3 in allCustomValues.Where((CustomSyncedValueBase config) => config.HasLocalBaseValue)) { customValuesBeingApplied.Add(item3); try { item3.BoxedValue = item3.LocalBaseValue; item3.ClearLocalBaseValue(); } catch (Exception arg2) { DebugWarning("Reset", $"Failed to restore local custom value '{item3.Identifier}'; continuing. Error: {arg2}"); } finally { customValuesBeingApplied.Remove(item3); } } ServerLockedSettingChanged(); foreach (PolicyStateChangedEventArgs item4 in list) { RaisePolicyStateEvents(item4); } } private static OwnConfigEntryBase? GetConfigData(ConfigEntryBase config) { return config.Description.Tags?.OfType<OwnConfigEntryBase>().SingleOrDefault(); } [Description("Returns the synchronization wrapper attached to a registered BepInEx config entry.")] public static SyncedConfigEntry<T>? ConfigData<T>(ConfigEntry<T> config) { return ((ConfigEntryBase)config).Description.Tags?.OfType<SyncedConfigEntry<T>>().SingleOrDefault(); } private static T GetConfigAttribute<T>(ConfigEntryBase config) { return config.Description.Tags.OfType<T>().First(); } private static void EnsureDebugSupportInitialized() { lock (debugSupportLock) { if (!debugSupportInitialized) { debugSupportInitialized = true; LoadDebugConfig(createIfMissing: true, quiet: true); StartDebugConfigWatcher(); } } } private static void StartDebugConfigWatcher() { try { EnsureConfigDirectory(); debugConfigWatcher = new FileSystemWatcher(ConfigDirectoryPath, "ConditionalConfigSync.Debug.cfg") { NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite | NotifyFilters.CreationTime), EnableRaisingEvents = true }; debugConfigWatcher.Changed += delegate { ScheduleDebugConfigReload(); }; debugConfigWatcher.Created += delegate { ScheduleDebugConfigReload(); }; debugConfigWatcher.Renamed += delegate { ScheduleDebugConfigReload(); }; debugConfigWatcher.Deleted += delegate { ScheduleDebugConfigReload(); }; } catch (Exception ex) { LogSource.LogWarning((object)("[DebugConfig] Could not start watcher for ConditionalConfigSync.Debug.cfg: " + ex.Message)); } } private static void ScheduleDebugConfigReload() { lock (debugSupportLock) { if (debugConfigReloadScheduled) { return; } debugConfigReloadScheduled = true; } ThreadPool.QueueUserWorkItem(delegate { Thread.Sleep(500); lock (debugSupportLock) { debugConfigReloadScheduled = false; } LoadDebugConfig(createIfMissing: false, quiet: false); }); } private static void LoadDebugConfig(bool createIfMissing, bool quiet) { try { EnsureConfigDirectory(); if (!File.Exists(DebugConfigPath)) { if (createIfMissing) { File.WriteAllText(DebugConfigPath, "[Debug]\n# Local debug logging for ConditionalConfigSync. This file is not synchronized.\nEnabled = false\nLevel = Basic\nFilter = \n"); return; } debugConfigEnabled = false; debugConfigLevel = ConditionalConfigSyncDebugLevel.Basic; debugConfigFilter = ""; if (!quiet && DebugLoggingEnabled) { LogSource.LogInfo((object)"[DebugConfig] ConditionalConfigSync.Debug.cfg was deleted; file-based debug settings were reset."); } return; } Dictionary<string, string> dictionary = ReadSimpleDebugConfig(DebugConfigPath); bool flag = ReadBool(dictionary, "Enabled", fallback: false); string value; ConditionalConfigSyncDebugLevel conditionalConfigSyncDebugLevel = ReadDebugLevel(dictionary.TryGetValue("Level", out value) ? value : null, ConditionalConfigSyncDebugLevel.Basic); string value2; string text = (dictionary.TryGetValue("Filter", out value2) ? value2.Trim() : ""); debugConfigEnabled = flag; debugConfigLevel = conditionalConfigSyncDebugLevel; debugConfigFilter = text; if (!quiet && (flag || DebugLoggingEnabled)) { LogSource.LogInfo((object)string.Format("[DebugConfig] Reloaded {0}: enabled={1}, level={2}, filter='{3}'", "ConditionalConfigSync.Debug.cfg", flag, conditionalConfigSyncDebugLevel, text)); } } catch (Exception ex) { LogSource.LogWarning((object)("[DebugConfig] Failed to read ConditionalConfigSync.Debug.cfg: " + ex.Message)); } } private static Dictionary<string, string> ReadSimpleDebugConfig(string path) { Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); string text = ""; string[] array = ReadAllLinesStable(path); foreach (string text2 in array) { string text3 = text2.Trim(); if (text3.Length == 0 || text3.StartsWith("#") || text3.StartsWith(";")) { continue; } if (text3.StartsWith("[") && text3.EndsWith("]")) { text = text3.Substring(1, text3.Length - 2).Trim(); } else if (text.Equals("Debug", StringComparison.OrdinalIgnoreCase)) { int num = text3.IndexOf('='); if (num >= 0) { dictionary[text3.Substring(0, num).Trim()] = text3.Substring(num + 1).Trim(); } } } return dictionary; } private static bool ReadBool(Dictionary<string, string> values, string key, bool fallback) { if (!values.TryGetValue(key, out string value)) { return fallback; } if (bool.TryParse(value, out var result)) { return result; } if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2)) { return result2 != 0; } return fallback; } private static ConditionalConfigSyncDebugLevel ReadDebugLevel(string? raw, ConditionalConfigSyncDebugLevel fallback) { ConditionalConfigSyncDebugLevel result; return Enum.TryParse<ConditionalConfigSyncDebugLevel>(raw, ignoreCase: true, out result) ? result : fallback; } private bool ShouldDebugLog(ConditionalConfigSyncDebugLevel level) { if (!IsDebugActive || level > EffectiveDebugLevel) { return false; } string effectiveDebugFilter = EffectiveDebugFilter; if (string.IsNullOrWhiteSpace(effectiveDebugFilter)) { return true; } string text = DisplayName ?? Name; return text.IndexOf(effectiveDebugFilter, StringComparison.OrdinalIgnoreCase) >= 0 || Name.IndexOf(effectiveDebugFilter, StringComparison.OrdinalIgnoreCase) >= 0; } private void DebugLog(ConditionalConfigSyncDebugLevel level, string area, string message) { if (ShouldDebugLog(level)) { LogSource.LogInfo((object)("[" + GetDebugModName() + "][" + GetDebugSide() + "][" + area + "] " + message)); } } private void InfoLog(string message) { LogSource.LogInfo((object)("[" + GetDebugModName() + "][" + GetDebugSide() + "] " + message)); } private void DebugWarning(string area, string message) { LogSource.LogWarning((object)("[" + GetDebugModName() + "][" + GetDebugSide() + "][" + area + "] " + message)); } private string GetDebugModName() { return (!string.IsNullOrWhiteSpace(DisplayName)) ? DisplayName : Name; } private static string GetDebugSide() { if (!GameReflection.HasZNet) { return "NoZNet"; } return GameReflection.IsServer() ? "Server" : "Client"; } private void InvokeEventHandlers(Action? handlers, string eventName) { if (handlers == null) { return; } Delegate[] invocationList = handlers.GetInvocationList(); foreach (Delegate obj in invocationList) { try { ((Action)obj)(); } catch (Exception arg) { DebugWarning("Event", $"Subscriber of {eventName} failed; continuing. Error: {arg}"); } } } private void InvokeEventHandlers<T>(Action<T>? handlers, T value, string eventName) { if (handlers == null) { return; } Delegate[] invocationList = handlers.GetInvocationList(); foreach (Delegate obj in invocationList) { try { ((Action<T>)obj)(value); } catch (Exception arg) { DebugWarning("Event", $"Subscriber of {eventName} failed; continuing. Error: {arg}"); } } } private void InvokeEventHandlers<T1, T2>(Action<T1, T2>? handlers, T1 value1, T2 value2, string eventName) { if (handlers == null) { return; } Delegate[] invocationList = handlers.GetInvocationList(); foreach (Delegate obj in invocationList) { try { ((Action<T1, T2>)obj)(value1, value2); } catch (Exception arg) { DebugWarning("Event", $"Subscriber of {eventName} failed; continuing. Error: {arg}"); } } } private void InvokeEventHandlers<TEventArgs>(EventHandler<TEventArgs>? handlers, TEventArgs args, string eventName) where TEventArgs : EventArgs { if (handlers == null) { return; } Delegate[] invocationList = handlers.GetInvocationList(); foreach (Delegate obj in invocationList) { try { ((EventHandler<TEventArgs>)obj)(this, args); } catch (Exception arg) { DebugWarning("Event", $"Subscriber of {eventName} failed; continuing. Error: {arg}"); } } } private void RaiseInitialSyncCompletedIfNeeded() { if (!InitialSyncDone) { InitialSyncDone = true; InvokeEventHandlers(this.InitialSyncCompleted, "InitialSyncCompleted"); } } private void RaisePolicyStateEvents(OwnConfigEntryBase config, bool oldServerControlled, bool newServerControlled, bool oldHidden, bool newHidden, string source) { if (oldServerControlled != newServerControlled || oldHidden != newHidden) { RaisePolicyStateEvents(new PolicyStateChangedEventArgs(config, oldServerControlled, newServerControlled, oldHidden, newHidden, source)); } } private void RaisePolicyStateEvents(PolicyStateChangedEventArgs args) { InvokeEventHandlers(this.PolicyStateChanged, args, "PolicyStateChanged"); if (args.OldServerControlled != args.NewServerControlled) { InvokeEventHandlers(this.ServerControlledChanged, args.Config, args.NewServerControlled, "ServerControlledChanged"); } if (args.OldHidden != args.NewHidden) { InvokeEventHandlers(this.HiddenStateChanged, args.Config, args.NewHidden, "HiddenStateChanged"); } } private void RaiseLockStateChangedIfNeeded() { bool isLocked = IsLocked; if (lastNotifiedLockState != isLocked) { lastNotifiedLockState = isLocked; InvokeEventHandlers(this.LockStateChanged, isLocked, "LockStateChanged"); } } private void RejectSync(string reason, long? senderUid, bool incoming, Exception? exception = null) { DebugWarning("Reject", (exception == null) ? reason : $"{reason}{Environment.NewLine}{exception}"); InvokeEventHandlers(this.SyncRejected, new SyncRejectedEventArgs(reason, senderUid, incoming, exception), "SyncRejected"); } private static void RegisterDebugConsoleCommands() { if (debugCommandsRegistered) { return; } try { MethodInfo handler = AccessTools.DeclaredMethod(typeof(ConditionalConfigSync), "HandleDebugConsoleCommand", (Type[])null, (Type[])null) ?? throw new MissingMethodException(typeof(ConditionalConfigSync).FullName, "HandleDebugConsoleCommand"); MethodInfo handler2 = AccessTools.DeclaredMethod(typeof(ConditionalConfigSync), "HandlePolicyConsoleCommand", (Type[])null, (Type[])null) ?? throw new MissingMethodException(typeof(ConditionalConfigSync).FullName, "HandlePolicyConsoleCommand"); GameReflection.RegisterConsoleCommand("conditionalconfigsync_debug", "ConditionalConfigSync debug logging: status/on/off/level/filter/clearfilter", handler); GameReflection.RegisterConsoleCommand("conditionalconfigsync_debug_server", "ConditionalConfigSync server debug logging: status/on/off/level/filter/clearfilter", handler, isCheat: false, isNetwork: true, onlyServer: true, isSecret: false, allowInDevBuild: false, remoteCommand: true, onlyAdmin: true); RegisterPolicyConsoleCommand("conditionalconfigsync_status", "Show registered mods, config modes, and effective policy counts", handler2); RegisterPolicyConsoleCommand("conditionalconfigsync_policy_reload", "Reload and apply ConditionalConfigSync policy files", handler2); RegisterPolicyConsoleCommand("conditionalconfigsync_policy_validate", "Validate policy syntax, identifiers, and ignored rules", handler2); RegisterPolicyConsoleCommand("conditionalconfigsync_policy_dump", "Write a copy-ready policy identifier file", handler2); debugCommandsRegistered = true; } catch (Exception ex) { LogSource.LogWarning((object)("[Console] Could not register ConditionalConfigSync commands: " + ex.Message)); } } private static void RegisterPolicyConsoleCommand(string command, string description, MethodInfo handler) { GameReflection.RegisterConsoleCommand(command, description, handler, isCheat: false, isNetwork: true, onlyServer: true, isSecret: false, allowInDevBuild: false, remoteCommand: true, onlyAdmin: true); } private static object HandleDebugConsoleCommand(ConsoleEventArgs args) { EnsureDebugSupportInitialized(); bool flag = GameReflection.ConsoleArgsLength(args) <= 1; bool? flag2 = null; ConditionalConfigSyncDebugLevel? conditionalConfigSyncDebugLevel = null; string text = null; for (int i = 1; i < GameReflection.ConsoleArgsLength(args); i++) { string text2 = GameReflection.ConsoleArg(args, i).Trim(); switch (text2.ToLowerInvariant()) { case "status": flag = true; break; case "on": case "enable": case "enabled": flag2 = true; flag = false; break; case "off": case "disable": case "disabled": flag2 = false; flag = false; break; case "basic": case "verbose": case "trace": conditionalConfigSyncDebugLevel = ReadDebugLevel(text2, DebugLoggingLevel); flag = false; break; case "level": if (i + 1 < GameReflection.ConsoleArgsLength(args)) { conditionalConfigSyncDebugLevel = ReadDebugLevel(GameReflection.ConsoleArg(args, ++i), DebugLoggingLevel); flag = false; } break; case "filter": if (i + 1 < GameReflection.ConsoleArgsLength(args)) { text = GameReflection.ConsoleArg(args, ++i); flag = false; } break; case "clearfilter": case "nofilter": text = ""; flag = false; break; default: return "Unknown argument '" + text2 + "'"; } } if (flag) { WriteDebugStatus(args); return true; } DebugLoggingEnabled = flag2 ?? DebugLoggingEnabled; DebugLoggingLevel = conditionalConfigSyncDebugLevel ?? DebugLoggingLevel; DebugLoggingFilter = text ?? DebugLoggingFilter; AddConsoleLine(args, $"ConditionalConfigSync debug: enabled={DebugLoggingEnabled}, level={DebugLoggingLevel}, filter='{DebugLoggingFilter}'"); return true; } private static void WriteDebugStatus(ConsoleEventArgs args) { AddConsoleLine(args, $"ConditionalConfigSync debug: enabled={DebugLoggingEnabled}, configEnabled={debugConfigEnabled}, level={EffectiveDebugLevel}, filter='{EffectiveDebugFilter}'"); } private static void AddConsoleLine(ConsoleEventArgs args, string text) { GameReflection.ConsoleAddString(args, text); LogSource.LogInfo((object)("[Console] " + text)); } internal static void VersionDebugLog(string area, string modName, string message) { EnsureDebugSupportInitialized(); if (IsDebugActive && ConditionalConfigSyncDebugLevel.Basic <= EffectiveDebugLevel) { string effectiveDebugFilter = EffectiveDebugFilter; if (string.IsNullOrWhiteSpace(effectiveDebugFilter) || modName.IndexOf(effectiveDebugFilter, StringComparison.OrdinalIgnoreCase) >= 0) { LogSource.LogInfo((object)("[" + modName + "][" + GetDebugSide() + "][" + area + "] " + message)); } } } internal static void VersionInfoLog(string area, string modName, string message) { LogSource.LogInfo((object)("[" + modName + "][" + GetDebugSide() + "][" + area + "] " + message)); } internal static void VersionWarningLog(string area, string modName, string message) { LogSource.LogWarning((object)("[" + modName + "][" + GetDebugSide() + "][" + area + "] " + message)); } private void ApplyParsedConfigs(ParsedConfigs configs, bool receivedFromServer) { DebugLog(ConditionalConfigSyncDebugLevel.Verbose, "Apply", $"Applying configs={configs.configValues.Count}, custom={configs.customValues.Count}, states={configs.configStates.Count}"); Dictionary<ConfigFile, bool> saveOnConfigSet = new Dictionary<ConfigFile, bool>(); List<PolicyStateChangedEventArgs> list = new List<PolicyStateChangedEventArgs>(); try { if (receivedFromServer) { foreach (KeyValuePair<OwnConfigEntryBase, ReceivedConfigState> configState in configs.configStates) { bool isServerControlled = configState.Key.IsServerControlled; bool isHidden = configState.Key.IsHidden; configState.Key.IsServerControlled = configState.Value.ServerControlled; configState.Key.IsHidden = configState.Value.Hidden; configState.Key.PolicyStateInitialized = true; if (isServerControlled != configState.Value.ServerControlled || isHidden != configState.Value.Hidden) { list.Add(new PolicyStateChangedEventArgs(configState.Key, isServerControlled, configState.Value.ServerControlled, isHidden, configState.Value.Hidden, "server package")); } } } foreach (KeyValuePair<OwnConfigEntryBase, object> configValue in configs.configValues) { if (receivedFromServer) { configValue.Key.StoreServerValue(configValue.Value); if (!configValue.Key.IsServerControlled) { RestoreLocalValueIfNeeded(configValue.Key); continue; } if (!configValue.Key.HasLocalBaseValue) { configValue.Key.StoreLocalBaseValue(configValue.Key.BaseConfig.BoxedValue); } } SetConfigValue(configValue.Key, configValue.Value); } if (receivedFromServer) { foreach (KeyValuePair<OwnConfigEntryBase, ReceivedConfigState> configState2 in configs.configStates) { if (!configs.configValues.ContainsKey(configState2.Key)) { if (configState2.Key.IsServerControlled) { ApplyServerValueIfAvailable(configState2.Key); } else { RestoreLocalValueIfNeeded(configState2.Key); } } } } } finally { foreach (KeyValuePair<ConfigFile, bool> item in saveOnConfigSet) { item.Key.SaveOnConfigSet = item.Value; try { item.Key.Save(); } catch (Exception arg) { DebugWarning("Apply", $"Failed to save config file '{item.Key.ConfigFilePath}' after applying synchronized values: {arg}"); } } } foreach (KeyValuePair<CustomSyncedValueBase, object> customValue in configs.customValues) { if (!isServer && !customValue.Key.HasLocalBaseValue) { customValue.Key.StoreLocalBaseValue(customValue.Key.BoxedValue); } customValuesBeingApplied.Add(customValue.Key); try { customValue.Key.BoxedValue = customValue.Value; } catch (Exception arg2) { DebugWarning("Apply", $"Failed to apply custom value '{customValue.Key.Identifier}'; continuing with the remaining entries. Error: {arg2}"); } finally { customValuesBeingApplied.Remove(customValue.Key); } } if (receivedFromServer) { foreach (ConditionalConfigSync configSync in configSyncs) { configSync.ServerLockedSettingChanged(); } } foreach (PolicyStateChangedEventArgs item2 in list) { RaisePolicyStateEvents(item2); } void ApplyServerValueIfAvailable(OwnConfigEntryBase config) { if (config.HasServerValue) { if (!config.HasLocalBaseValue) { config.StoreLocalBaseValue(config.BaseConfig.BoxedValue); } SetConfigValue(config, config.ServerValue); } } void DisableSaveOnConfigSet(ConfigFile configFile) { if (!saveOnConfigSet.ContainsKey(configFile)) { saveOnConfigSet[configFile] = configFile.SaveOnConfigSet; configFile.SaveOnConfigSet = false; } } void RestoreLocalValueIfNeeded(OwnConfigEntryBase config) { if (config.HasLocalBaseValue && SetConfigValue(config, config.LocalBaseValue)) { config.ClearLocalBaseValue(); } } bool SetConfigValue(OwnConfigEntryBase config, object? value) { ConfigFile configFile = config.BaseConfig.ConfigFile; DisableSaveOnConfigSet(configFile); configsBeingApplied.Add(config.BaseConfig); try { config.BaseConfig.BoxedValue = value; return true; } catch (Exception arg3) { ConfigDefinition definition = config.BaseConfig.Definition; DebugWarning("Apply", $"Failed to apply config {definition.Section} -> {definition.Key}; continuing with the remaining entries. Error: {arg3}"); return false; } finally { configsBeingApplied.Remove(config.BaseConfig); } } } private static string GetSingleEntryReceiveDetails(ParsedConfigs configs) { List<string> list = new List<string>(); if (configs.configValues.Count == 1) { ConfigDefinition definition = configs.configValues.Keys.First().BaseConfig.Definition; list.Add("config=" + definition.Section + " -> " + definition.Key); } if (configs.customValues.Count == 1) { list.Add("custom=" + configs.customValues.Keys.First().Identifier); } return (list.Count == 0) ? "" : (" (" + string.Join(", ", list) + ")"); } private ParsedConfigs ReadConfigsFromPackage(ZPackage package, bool receivedFromServer) { ParsedConfigs parsedConfigs = new ParsedConfigs(); Dictionary<string, OwnConfigEntryBase> dictionary = allConfigs.ToDictionary((OwnConfigEntryBase c) => c.BaseConfig.Definition.Section + "\n" + c.BaseConfig.Definition.Key, (OwnConfigEntryBase c) => c); Dictionary<string, CustomSyncedValueBase> dictionary2 = allCustomValues.ToDictionary((CustomSyncedValueBase c) => c.Identifier, (CustomSyncedValueBase c) => c); int num = GameReflection.PackageReadInt(package); if (num < 0 || num > 8192) { throw new InvalidDataException($"Invalid config entry count {num}"); } for (int num2 = 0; num2 < num; num2++) { PackageEntryKind packageEntryKind = (PackageEntryKind)GameReflection.PackageReadByte(package); byte[] array = GameReflection.PackageReadByteArray(package, 20971520); if (array.Length > 20971520) { DebugWarning("Read", $"Skipping too large entry payload ({array.Length})"); continue; } ZPackage package2 = GameReflection.NewPackage(array); switch (packageEntryKind) { case PackageEntryKind.Config: { string text = GameReflection.PackageReadString(package2); string text2 = GameReflection.PackageReadString(package2); string text3 = GameReflection.PackageReadString(package2); if (dictionary.TryGetValue(text + "\n" + text2, out var value)) { try { parsedConfigs.configValues[value] = TomlTypeConverter.ConvertToValue(text3, value.BaseConfig.SettingType); } catch (Exception ex) { DebugWarning("Read", $"Config value of setting \"{value.BaseConfig.Definition}\" could not be parsed and will be ignored. Reason: {ex.Message}"); } } else { DebugWarning("Read", "Received unknown config entry " + text + "/" + text2 + ". This may happen if client and server versions of the mod do not match."); } break; } case PackageEntryKind.ConfigState: { string text5 = GameReflection.PackageReadString(package2); string text6 = GameReflection.PackageReadString(package2); bool serverControlled = GameReflection.PackageReadBool(package2); bool hidden = GameReflection.PackageReadBool(package2); if (dictionary.TryGetValue(text5 + "\n" + text6, out var value2)) { parsedConfigs.configStates[value2] = new ReceivedConfigState(serverControlled, hidden); break; } DebugWarning("Read", "Received unknown config state " + text5 + "/" + text6 + ". This may happen if client and server versions of the mod do not match."); break; } case PackageEntryKind.CustomValue: { string text7 = GameReflection.PackageReadString(package2); string text8 = GameReflection.PackageReadString(package2); if (!dictionary2.TryGetValue(text7, out var value3)) { DebugWarning("Read", "Received unknown custom synced value " + text7 + ". This may happen if client and server versions of the mod do not match."); break; } string zPackageTypeString = GetZPackageTypeString(value3.Type); if (text8 != zPackageTypeString) { DebugWarning("Read", "Got unexpected type " + text8 + " for custom synced value " + text7 + ", expecting " + zPackageTypeString); break; } try { parsedConfigs.customValues[value3] = ReadCustomValueFromPackage(package2, value3.Type); } catch (InvalidDeserializationTypeException ex2) { DebugWarning("Read", "Got unexpected struct internal type " + ex2.received + " for field " + ex2.field + " struct " + text8 + " for custom synced value " + text7 + ", expecting " + ex2.expected); } catch (Exception arg) { DebugWarning("Read", $"Could not deserialize custom synced value {text7}: {arg}"); } break; } case PackageEntryKind.ServerVersion: { string text4 = GameReflection.PackageReadString(package2); if (receivedFromServer && text4 != CurrentVersion) { DebugWarning("Version", "Received server version is not equal: server version=" + text4 + ", local version=" + (CurrentVersion ?? "unknown")); } break; } case PackageEntryKind.LockExempt: { bool flag = GameReflection.PackageReadBool(package2); if (receivedFromServer) { lockExempt = flag; } break; } default: DebugWarning("Read", $"Received unknown config entry kind {(byte)packageEntryKind}"); break; } } return parsedConfigs; } private static ZPackage ConfigsToPackage(IEnumerable<ConfigEntryBase>? configs = null, IEnumerable<CustomSyncedValueBase>? customValues = null, IEnumerable<PackageEntry>? packageEntries = null, bool partial = true, bool includeConfigValues = true, bool includeAllProvidedConfigStates = false) { List<OwnConfigEntryBase> list = new List<OwnConfigEntryBase>(); if (configs != null) { foreach (ConfigEntryBase config in configs) { OwnConfigEntryBase configData = GetConfigData(config); if (configData != null && (includeAllProvidedConfigStates || ShouldIncludeConfigInPackage(configData)) && !list.Contains(configData)) { list.Add(configData); } } } List<OwnConfigEntryBase> list2 = (includeConfigValues ? list.Where(GetPackageServerControlled).ToList() : new List<OwnConfigEntryBase>()); List<CustomSyncedValueBase> list3 = customValues?.ToList() ?? new List<CustomSyncedValueBase>(); List<PackageEntry> list4 = packageEntries?.ToList() ?? new List<PackageEntry>(); ZPackage val = GameReflection.NewPackage(); GameReflection.PackageWrite(val, (byte)((partial ? 1 : 0) | 8)); GameReflection.PackageWrite(val, list2.Count + list.Count + list3.Count + list4.Count); foreach (PackageEntry item in list4) { AddEntryToPackage(val, item); } foreach (OwnConfigEntryBase item2 in list) { AddEntryToPackage(val, PackageEntry.ConfigState(item2.BaseConfig, GetPackageServerControlled(item2), GetPackageHidden(item2))); } foreach (CustomSyncedValueBase item3 in list3) { AddEntryToPackage(val, new PackageEntry { kind = PackageEntryKind.CustomValue, key = item3.Identifier, type = item3.Type, value = item3.BoxedValue }); } foreach (OwnConfigEntryBase item4 in list2) { AddEntryToPackage(val, new PackageEntry { kind = PackageEntryKind.Config, section = item4.BaseConfig.Definition.Section, key = item4.BaseConfig.Definition.Key, type = item4.BaseConfig.SettingType, value = item4.BaseConfig.BoxedValue }); } return val; } private static void AddEntryToPackage(ZPackage package, PackageEntry entry) { ZPackage package2 = GameReflection.NewPackage(); switch (entry.kind) { case PackageEntryKind.Config: GameReflection.PackageWrite(package2, entry.section); GameReflection.PackageWrite(package2, entry.key); GameReflection.PackageWrite(package2, TomlTypeConverter.ConvertToString(entry.value, entry.type)); break; case PackageEntryKind.CustomValue: GameReflection.PackageWrite(package2, entry.key); GameReflection.PackageWrite(package2, GetZPackageTypeString(entry.type)); WriteCustomValueToPackage(package2, entry.type, entry.value); break; case PackageEntryKind.ServerVersion: GameReflection.PackageWrite(package2, entry.value?.ToString() ?? ""); break; case PackageEntryKind.LockExempt: { object value = entry.value; bool flag = default(bool); int num; if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } GameReflection.PackageWrite(package2, (byte)((uint)num & (flag ? 1u : 0u)) != 0); break; } case PackageEntryKind.ConfigState: GameReflection.PackageWrite(package2, entry.section); GameReflection.PackageWrite(package2, entry.key); GameReflection.PackageWrite(package2, entry.serverControlled); GameReflection.PackageWrite(package2, entry.hidden); break; default: throw new ArgumentOutOfRangeException("kind", entry.kind, null); } GameReflection.PackageWrite(package, (byte)entry.kind); GameReflection.PackageWrite(package, GameReflection.PackageGetArray(package2)); } private static string GetZPackageTypeString(Type type) { return type.AssemblyQualifiedName; } private static void WriteCustomValueToPackage(ZPackage package, Type type, object? value) { GameReflection.PackageWrite(package, value != null); if (value != null) { WriteValueWithTypeToZPackage(package, type, value); } } private static object? ReadCustomValueFromPackage(ZPackage package, Type type) { if (!GameReflection.PackageReadBool(package)) { return null; } return ReadValueWithTypeFromZPackage(package, type); } private static void WriteValueWithTypeToZPackage(ZPackage package, Type type, object value) { Type type2 = Nullable.GetUnderlyingType(type) ?? type; if (typeof(ISerializableParameter).IsAssignableFrom(type2)) { GameReflection.SerializeParameter(value, ref package); return; } if (type2.IsEnum) { WriteValueWithTypeToZPackage(package, Enum.GetUnderlyingType(type2), ((IConvertible)value).ToType(Enum.GetUnderlyingType(type2), CultureInfo.InvariantCulture)); return; } if (value is ICollection collection && type2 != typeof(string) && type2 != typeof(List<string>)) { GameReflection.PackageWrite(package, collection.Count); { foreach (object item in collection) { WriteValueWithTypeToZPackage(package, item.GetType(), item); } return; } } if ((object)type2 != null && type2.IsValueType && !type2.IsPrimitive) { FieldInfo[] fields = type2.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); GameReflection.PackageWrite(package, fields.Length); FieldInfo[] array = fields; foreach (FieldInfo fieldInfo in array) { GameReflection.PackageWrite(package, GetZPackageTypeString(fieldInfo.FieldType)); object value2 = fieldInfo.GetValue(value); WriteCustomValueToPackage(package, fieldInfo.FieldType, value2); } } else { GameReflection.Serialize(new object[1] { value }, ref package); } } private static object ReadValueWithTypeFromZPackage(ZPackage package, Type type) { Type type2 = Nullable.GetUnderlyingType(type) ?? type; if (typeof(ISerializableParameter).IsAssignableFrom(type2)) { object obj = Activator.CreateInstance(type2) ?? throw new MissingMethodException("Cannot create " + type2.FullName + " for ISerializableParameter deserialization"); GameReflection.DeserializeParameter(obj, ref package); return obj; } if (type2.IsEnum) { object value = ReadValueWithTypeFromZPackage(package, Enum.GetUnderlyingType(type2)); return Enum.ToObject(type2, value); } if ((object)type2 != null && type2.IsValueType && !type2.IsPrimitive) { FieldInfo[] fields = type2.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); int num = GameReflection.PackageReadInt(package); if (num != fields.Length) { throw new InvalidDeserializationTypeException { received = $"(field count: {num})", expected = $"(field count: {fields.Length})" }; } object uninitializedObject = FormatterServices.GetUninitializedObject(type2); FieldInfo[] array = fields; foreach (FieldInfo fieldInfo in array) { string text = GameReflection.PackageReadString(package); if (text != GetZPackageTypeString(fieldInfo.FieldType)) { throw new InvalidDeserializationTypeException { received = text, expected = GetZPackageTypeString(fieldInfo.FieldType), field = fieldInfo.Name }; } fieldInfo.SetValue(uninitializedObject, ReadCustomValueFromPackage(package, fieldInfo.FieldType)); } return uninitializedObject; } if (type2.IsGenericType && type2.GetGenericTypeDefinition() == typeof(Dictionary<, >)) { int num2 = GameReflection.PackageReadInt(package); IDictionary dictionary = (IDictionary)Activator.CreateInstance(type2); Type type3 = typeof(KeyValuePair<, >).MakeGenericType(type2.GenericTypeArguments); FieldInfo field = type3.GetField("key", BindingFlags.Instance | BindingFlags.NonPublic); FieldInfo field2 = type3.GetField("value", BindingFlags.Instance | BindingFlags.NonPublic); for (int j = 0; j < num2; j++) { object obj2 = ReadValueWithTypeFromZPackage(package, type3); dictionary.Add(field.GetValue(obj2), field2.GetValue(obj2)); } return dictionary; } if (type2 != typeof(List<string>) && type2.IsGenericType) { Type type4 = typeof(ICollection<>).MakeGenericType(type2.GenericTypeArguments[0]); if ((object)type4 != null && type4.IsAssignableFrom(type2)) { int num3 = GameReflection.PackageReadInt(package); object obj3 = Activator.CreateInstance(type2); MethodInfo method = type4.GetMethod("Add"); for (int k = 0; k < num3; k++) { method.Invoke(obj3, new object[1] { ReadValueWithTypeFromZPackage(package, type2.GenericTypeArguments[0]) }); } return obj3; } } ParameterInfo parameterInfo = (ParameterInfo)FormatterServices.GetUninitializedObject(typeof(ParameterInfo)); AccessTools.DeclaredField(typeof(ParameterInfo), "ClassImpl").SetValue(parameterInfo, type2); List<object> data = new List<object>(); GameReflection.Deserialize(new ParameterInfo[2] { null, parameterInfo }, package, ref data); return data.First(); } private static ArraySegment<byte> GetPackageArraySegment(ZPackage package) { if (GameReflection.PackageStream(package).TryGetBuffer(out var buffer)) { return new ArraySegment<byte>(buffer.Array, buffer.Offset, GameReflection.PackageSize(package)); } byte[] array = GameReflection.PackageGetArray(package); return new ArraySegment<byte>(array, 0, array.Length); } private static void WriteByteArray(ZPackage package, ArraySegment<byte> data) { GameReflection.PackageWrite(package, data.Count); GameReflection.PackageStream(package).Write(data.Array, data.Offset, data.Count); } private static byte[] DecompressLimited(byte[] data, int maxBytes) { using MemoryStream stream = new MemoryStream(data); using DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress); using MemoryStream memoryStream = new MemoryStream(); byte[] array = new byte[81920]; while (true) { int num = deflateStream.Read(array, 0, array.Length); if (num <= 0) { break; } if (memoryStream.Length + num > maxBytes) { throw new InvalidDataException($"Decompressed package exceeds limit {maxBytes}"); } memoryStream.Write(array, 0, num); } return memoryStream.ToArray(); } private static void EnsurePolicySupportInitialized(bool createIfMissing = false) { bool flag; lock (policyLock) { flag = !policySupportInitialized || syncPolicyWatcher == null || hiddenConfigsWatcher == null; policySupportInitialized = true; } LoadPolicyFiles(createIfMissing, quiet: true, "server startup"); if (flag) { StartPolicyWatchers(); } } private static void StartPolicyWatchers() { try { EnsureConfigDirectory(); if (syncPolicyWatcher == null) { syncPolicyWatcher = CreatePolicyWatcher("ConditionalConfigSync.SyncPolicy.cfg"); } if (hiddenConfigsWatcher == null) { hiddenConfigsWatcher = CreatePolicyWatcher("ConditionalConfigSync.HiddenConfigs.cfg"); } } catch (Exception ex) { LogSource.LogWarning((object)("[Policy] Could not start policy watchers: " + ex.Message)); } } private static FileSystemWatcher CreatePolicyWatcher(string fileName) { FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(ConfigDirectoryPath, fileName) { NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite | NotifyFilters.CreationTime), EnableRaisingEvents = true }; fileSystemWatcher.Changed += delegate { SchedulePolicyReload(); }; fileSystemWatcher.Created += delegate { SchedulePolicyReload(); }; fileSystemWatcher.Renamed += delegate { SchedulePolicyReload(); }; fileSystemWatcher.Deleted += delegate { SchedulePolicyReload(); }; return fileSystemWatcher; } private static void SchedulePolicyReload() { long generation; lock (policyLock) { if (policyReloadScheduled) { return; } policyReloadScheduled = true; generation = ++policyReadGeneration; } ThreadPool.QueueUserWorkItem(delegate { Thread.Sleep(500); if (!TryReadPolicyFiles(createIfMissing: false, out Dictionary<string, ConfigPolicyOverride> newSyncPolicy, out HashSet<string> newHiddenPolicy, out List<SyncPolicyRecord> syncRecords, out List<HiddenPolicyRecord> hiddenRecords, out string error)) { lock (policyLock) { policyReloadScheduled = false; } LogSource.LogWarning((object)("[Policy] Failed to read policy files: " + error)); } else { lock (policyLock) { policyReloadScheduled = false; } EnqueueMainThread(delegate { ApplyPolicyFiles(newSyncPolicy, newHiddenPolicy, syncRecords, hiddenRecords, quiet: false, "policy file watcher", generation); }); } }); } private static bool LoadPolicyFiles(bool createIfMissing, bool quiet, string source) { if (!TryReadPolicyFiles(createIfMissing, out Dictionary<string, ConfigPolicyOverride> newSyncPolicy, out HashSet<string> newHiddenPolicy, out List<SyncPolicyRecord> syncRecords, out List<HiddenPolicyRecord> hiddenRecords, out string error)) { LogSource.LogWarning((object)("[Policy] Failed to read policy files: " + error)); return false; } long generation = Interlocked.Increment(ref policyReadGeneration); if (IsMainThread) { ApplyPolicyFiles(newSyncPolicy, newHiddenPolicy, syncRecords, hiddenRecords, quiet, source, generation); } else { EnqueueMainThread(delegate { ApplyPolicyFiles(newSyncPolicy, newHiddenPolicy, syncRecords, hiddenRecords, quiet, source, generation); }); } return true; } private static bool TryReadPolicyFiles(bool createIfMissing, out Dictionary<string, ConfigPolicyOverride> newSyncPolicy, out HashSet<string> newHiddenPolicy, out List<SyncPolicyRecord> syncRecords, out List<HiddenPolicyRecord> hiddenRecords, out string? error) { newSyncPolicy = new Dictionary<string, ConfigPolicyOverride>(StringComparer.OrdinalIgnoreCase); newHiddenPolicy = new HashSet<string>(StringComparer.OrdinalIgnoreCase); syncRecords = new List<SyncPolicyRecord>(); hiddenRecords = new List<HiddenPolicyRecord>(); error = null; try { EnsureConfigDirectory(); if (createIfMissing) { EnsurePolicyFilesExist(); } newSyncPolicy = ReadSyncPolicyFile(SyncPolicyPath, out syncRecords); newHiddenPolicy = ReadHiddenConfigsFile(HiddenConfigsPath, out hiddenRecords); return true; } catch (Exception ex) { error = ex.Message; return false; } } private static void ApplyPolicyFiles(Dictionary<string, ConfigPolicyOverride> newSyncPolicy, HashSet<string> newHiddenPolicy, List<SyncPolicyRecord> syncRecords, List<HiddenPolicyRecord> hiddenRecords, bool quiet, string source, long generation) { lock (policyLock) { if (generation < policyAppliedGeneration) { return; } policyAppliedGeneration = generation; syncPolicy = newSyncPolicy; hiddenConfigPolicy = newHiddenPolicy; } LogPolicyFileRecords(syncRecords, hiddenRecords, source); if (!quiet) { int num = newSyncPolicy.Count<KeyValuePair<string, ConfigPolicyOverride>>((KeyValuePair<string, ConfigPolicyOverride> kv) => kv.Value == ConfigPolicyOverride.ForceServerControlled); int num2 = newSyncPolicy.Count<KeyValuePair<string, ConfigPolicyOverride>>((KeyValuePair<string, ConfigPolicyOverride> kv) => kv.Value == ConfigPolicyOverride.ForceClientControlled); LogSource.LogInfo((object)$"[Policy] Reloaded: forceServer={num}, forceClient={num2}, hidden={newHiddenPolicy.Count}, source={source}"); } RefreshPolicyStatesForAll(source, !quiet); } private static void EnsurePolicyFilesExist() { if (!File.Exists(SyncPolicyPath)) { File.WriteAllText(SyncPolicyPath, "# ConditionalConfigSync sync policy. Server-side only.\n# Exact setting: + ModGuid.Section.Key or - ModGuid.Section.Key\n# Whole section: + ModGuid.Section or - ModGuid.Section\n# + forces server-controlled; - forces client-controlled.\n# Exact-setting rules take precedence over section rules.\n# Rules apply only to configs registered as Conditional.\n"); } if (!File.Exists(HiddenConfigsPath)) { File.WriteAllText(HiddenConfigsPath, "# ConditionalConfigSync hidden config policy. Server-side only.\n# Exact setting: ModGuid.Section.Key\n# Whole section: ModGuid.Section\n# Exact and section entries may be combined.\n"); } } private static Dictionary<string, ConfigPolicyOverride> ReadSyncPolicyFile(string path, out List<SyncPolicyRecord> records) { Dictionary<string, ConfigPolicyOverride> dictionary = new Dictionary<string, ConfigPolicyOverride>(StringComparer.OrdinalIgnoreCase); records = new List<SyncPolicyRecord>(); string[] array = ReadAllLinesStable(path, missingIsEmpty: true); foreach (string line in array) { string text = StripPolicyComment(line).Trim(); if (text.Length == 0) { continue; } char c = text[0]; if (c != '+' && c != '-') { records.Add(new SyncPolicyRecord(text, null, ConfigPolicyOverride.Default, "expected '+' or '-'")); continue; } string text2 = text.Substring(1).Trim(); if (text2.Length == 0) { records.Add(new SyncPolicyRecord(text, null, ConfigPolicyOverride.Default, "config record is empty")); continue; } ConfigPolicyOverride configPolicyOverride = ((c == '+') ? ConfigPolicyOverride.ForceServerControlled : ConfigPolicyOverride.ForceClientControlled); records.Add(new SyncPolicyRecord(text2, text2, configPolicyOverride, null)); dictionary[text2] = configPolicyOverride; } return dictionary; } private static HashSet<string> ReadHiddenConfigsFile(string path, out List<HiddenPolicyRecord> records) { HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); records = new List<HiddenPolicyRecord>(); string[] array = ReadAllLinesStable(path, missingIsEmpty: true); foreach (string line in array) { string text = StripPolicyComment(line).Trim(); if (text.Length != 0) { records.Add(new HiddenPolicyRecord(text, text, null)); hashSet.Add(text); } } return hashSet; } private static string StripPolicyComment(string line) { int num = line.IndexOf('#'); int num2 = line.IndexOf(';'); int num3 = ((num < 0) ? num2 : ((num2 < 0) ? num : Math.Min(num, num2))); return (num3 < 0) ? line : line.Substring(0, num3); } private static void RefreshPolicyStatesForAll(string source, bool broadcast) { foreach (ConditionalConfigSync configSync in configSyncs) { configSync.RefreshPolicyStates(source, broadcast); } } private void RefreshPolicyStates(string source, bool broadcast) { if (!IsSourceOfTruth) { return; } List<OwnConfigEntryBase> list = new List<OwnConfigEntryBase>(); List<PolicyStateChangedEventArgs> list2 = new List<PolicyStateChangedEventArgs>(); foreach (OwnConfigEntryBase allConfig in allConfigs) { bool flag = !allConfig.PolicyStateInitialized; bool flag2 = (flag ? GetDefaultServerControlled(allConfig) : allConfig.IsServerControlled); bool flag3 = !flag && allConfig.IsHidden; bool isServerControlled = allConfig.IsServerControlled; bool isHidden = allConfig.IsHidden; bool flag4 = ComputeServerControlled(allConfig); bool flag5 = ComputeHidden(allConfig); allConfig.IsServerControlled = flag4; allConfig.IsHidden = flag5; allConfig.PolicyStateInitialized = true; bool flag6 = isServerControlled != flag4 || isHidden != flag5; bool flag7 = flag2 != flag4 || flag3 != flag5; if (flag6 || flag7) { list.Add(allConfig); if (flag7) { list2.Add(new PolicyStateChangedEventArgs(allConfig, flag2, flag4, flag3, flag5, source)); } } } if (list.Count > 0) { ServerLockedSettingChanged(); } foreach (PolicyStateChangedEventArgs item in list2) { RaisePolicyStateEvents(item); } if (broadcast && isServer && GameReflection.HasZNet && list.Count > 0) { StartBroadcastPackage(GameReflection.Everybody, ConfigsToPackage(list.Select((OwnConfigEntryBase c) => c.BaseConfig), null, null, partial: true, includeConfigValues: true, includeAllProvidedConfigStates: true)); } } private static void LogPolicyFileRecords(IReadOnlyList<SyncPolicyRecord> syncRecords, IReadOnlyList<HiddenPolicyRecord> hiddenRecords, string source) { LogSyncPolicyRecords(syncRecords, source); LogHiddenPolicyRecords(hiddenRecords, source); } private static void LogSyncPolicyRecords(IReadOnlyList<SyncPolicyRecord> records, string source) { Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); for (int i = 0; i < records.Count; i++) { if (records[i].Key != null) { dictionary[records[i].Key] = i; } } for (int j = 0; j < records.Count; j++) { SyncPolicyRecord record = records[j]; string policySourceSuffix = GetPolicySourceSuffix(source); if (record.Error != null || record.Key == null) { LogSource.LogWarning((object)("[SyncPolicy] " + record.DisplayText + ": " + record.Error + policySourceSuffix)); continue; } PolicyTargetResolution policyTargetResolution = ResolvePolicyTarget(record.Key); if (LogPolicyTargetFailure(policyTargetResolution, record.DisplayText, "SyncPolicy", policySourceSuffix)) { continue; } ConditionalConfigSync configSync = policyTargetResolution.ConfigSync; string text = "[SyncPolicy][" + configSync.GetDebugModName() + "]"; string policyOverrideName = GetPolicyOverrideName(record.PolicyOverride); if (dictionary[record.Key] != j) { LogSource.LogWarning((object)(text + " " + record.DisplayText + ": overridden by a later rule" + policySourceSuffix)); continue; } string matchedKey; List<OwnConfigEntryBase> list = policyTargetResolution.Configs.Where((OwnConfigEntryBase config) => configSync.GetPolicyOverride(config, out matchedKey) != ConfigPolicyOverride.Default && string.Equals(matchedKey, record.Key, StringComparison.OrdinalIgnoreCase)).ToList(); if (list.Count == 0) { LogSource.LogInfo((object)(text + " " + record.DisplayText + ": " + policyOverrideName + " has no effect on mod behavior" + policySourceSuffix)); continue; } List<OwnConfigEntryBase> list2 = list.Where((OwnConfigEntryBase config) => config.SyncMode == ConfigSyncMode.Conditional).ToList(); if (list2.Count == 0) { ConfigSyncMode[] array = list.Select((OwnConfigEntryBase config) => config.SyncMode).Distinct().ToArray(); string text2 = ((array.Length == 1) ? $"ignored because its mode is {array[0]}" : "ignored because matching configs are not Conditional"); LogSource.LogInfo((object)(text + " " + record.DisplayText + ": " + text2 + policySourceSuffix)); continue; } bool targetServerControlled = record.PolicyOverride == ConfigPolicyOverride.ForceServerControlled; if (list2.Any((OwnConfigEntryBase config) => config.SynchronizedConfig != targetServerControlled)) { LogSource.LogWarning((object)(text + " " + record.DisplayText + ": " + policyOverrideName + " changes mod behavior" + policySourceSuffix)); } else { LogSource.LogInfo((object)(text + " " + record.DisplayText + ": " + policyOverrideName + " has no effect on mod behavior" + policySourceSuffix)); } } } private static void LogHiddenPolicyRecords(IReadOnlyList<HiddenPolicyRecord> records, string source) { foreach (HiddenPolicyRecord record in records) { string policySourceSuffix = GetPolicySourceSuffix(source); if (record.Error != null || record.Key == null) { LogSource.LogWarning((object)("[HiddenConfigs] " + record.DisplayText + ": " + record.Error + policySourceSuffix)); continue; } PolicyTargetResolution policyTargetResolution = ResolvePolicyTarget(record.Key); if (!LogPolicyTargetFailure(policyTargetResolution, record.DisplayText, "HiddenConfigs", policySourceSuffix)) { ConditionalConfigSync configSync = policyTargetResolution.ConfigSync; LogSource.LogWarning((object)("[HiddenConfigs][" + configSync.GetDebugModName() + "] " + record.DisplayText + ": is now hidden in configuration manager" + policySourceSuffix)); } } } private static bool LogPolicyTargetFailure(PolicyTargetResolution resolution, string displayText, string area, string sourceSuffix) { if (resolution.Failure == PolicyTargetFailure.None) { return false; } if (resolution.Failure == PolicyTargetFailure.Mod) { LogSource.LogWarning((object)("[" + area + "] " + displayText + ": can not find mod by GUID" + sourceSuffix)); return true; } string debugModName = resolution.ConfigSync.GetDebugModName(); string text = ((resolution.Failure == PolicyTargetFailure.Section) ? "can not find config section" : "can not find config name"); LogSource.LogWarning((object)("[" + area + "][" + debugModName + "] " + displayText + ": " + text + sourceSuffix)); return true; } private static PolicyTargetResolution ResolvePolicyTarget(string key) { PolicyTargetResolution policyTargetResolution = new PolicyTargetResolution(); ConditionalConfigSync configSync = (from sync in configSyncs where key.StartsWith(sync.Name + ".", StringComparison.OrdinalIgnoreCase) orderby sync.Name.Length descending select sync).FirstOrDefault(); if (configSync == null) { policyTargetResolution.Failure = PolicyTargetFailure.Mod; return policyTargetResolution; } policyTargetResolution.ConfigSync = configSync; OwnConfigEntryBase ownConfigEntryBase = configSync.allConfigs.FirstOrDefault((OwnConfigEntryBase config) => string.Equals(configSync.GetPolicyKey(config), key, StringComparison.OrdinalIgnoreCase)); if (ownConfigEntryBase != null) { policyTargetResolution.Configs.Add(ownConfigEntryBase); return policyTargetResolution; } List<OwnConfigEntryBase> list = configSync.allConfigs.Where((OwnConfigEntryBase config) => string.Equals(configSync.GetPolicySectionKey(config), key, StringComparison.OrdinalIgnoreCase)).ToList(); if (list.Count > 0) { policyTargetResolution.Configs.AddRange(list); return policyTargetResolution; } string remainder = key.Substring(configSync.Name.Length + 1); bool flag = configSync.allConfigs.Select((OwnConfigEntryBase config) => config.BaseConfig.Definition.Section).Distinct<string>(StringComparer.OrdinalIgnoreCase).Any((string section) => remainder.StartsWith(section + ".", StringComparison.OrdinalIgnoreCase)); policyTargetResolution.Failure = (flag ? PolicyTargetFailure.Config : PolicyTargetFailure.Section); return policyTargetResolution; } private static string GetPolicyOverrideName(ConfigPolicyOverride policyOverride) { return (policyOverride == ConfigPolicyOverride.ForceServerControlled) ? "ForceServerControlled" : "ForceClientControlled"; } private static string GetPolicySourceSuffix(string source) { return (IsDebugActive && EffectiveDebugLevel >= ConditionalConfigSyncDebugLevel.Verbose) ? (", source=" + source) : ""; } private static List<string> ValidatePolicyFiles(out int syncRuleCount, out int hiddenRuleCount) { List<string> list = new List<string>(); Dictionary<string, ConfigPolicyOverride> dictionary = new Dictionary<string, ConfigPolicyOverride>(StringComparer.OrdinalIgnoreCase); HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); EnsureConfigDirectory(); ValidateSyncPolicyFile(dictionary, list); ValidateHiddenPolicyFile(hashSet, list); syncRuleCount = dictionary.Count; hiddenRuleCount = hashSet.Count; HashSet<string> hashSet2 = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (ConditionalConfigSync configSync in configSyncs) { foreach (OwnConfigEntryBase allConfig in configSync.allConfigs) { hashSet2.Add(configSync.GetPolicyKey(allConfig)); hashSet2.Add(configSync.GetPolicySectionKey(allConfig)); } } foreach (string item in dictionary.Keys.Concat(hashSet).Distinct<string>(StringComparer.OrdinalIgnoreCase)) { if (!hashSet2.Contains(item)) { list.Add("Unknown identifier: " + item); } } foreach (ConditionalConfigSync configSync2 in configSyncs) { foreach (OwnConfigEntryBase allConfig2 in configSync2.allConfigs) { string matchedKey; ConfigPolicyOverride configPolicyOverride = ResolvePolicyOverride(dictionary, configSync2.GetPolicyKey(allConfig2), configSync2.GetPolicySectionKey(allConfig2), out matchedKey); if (configPolicyOverride != ConfigPolicyOverride.Default) { if (allConfig2 == configSync2.lockedConfig && configPolicyOverride == ConfigPolicyOverride.ForceClientControlled) { list.Add("ForceClientControlled is ignored for protected locking config " + configSync2.GetPolicyKey(allConfig2) + " (matched rule: " + matchedKey + ")."); } else if (allConfig2.SyncMode != ConfigSyncMode.Conditional) { list.Add($"Rule {matchedKey} is ignored for {configSync2.GetPolicyKey(allConfig2)} because its mode is {allConfig2.SyncMode}."); } } } } return list; } private static void ValidateSyncPolicyFile(Dictionary<string, ConfigPolicyOverride> result, List<string> diagnostics) { if (!File.Exists(SyncPolicyPath)) { diagnostics.Add("File does not exist: " + SyncPolicyPath); return; } string[] array = ReadAllLinesStable(SyncPolicyPath); for (int i = 0; i < array.Length; i++) { string text = array[i]; string text2 = StripPolicyComment(text).Trim(); if (text2.Length == 0) { continue; } char c = text2[0]; if (c != '+' && c != '-') { diagnostics.Add(string.Format("{0}:{1}: expected '+' or '-': {2}", "ConditionalConfigSync.SyncPolicy.cfg", i + 1, text)); continue; } string text3 = text2.Substring(1).Trim(); if (text3.Length == 0) { diagnostics.Add(string.Format("{0}:{1}: identifier is empty.", "ConditionalConfigSync.SyncPolicy.cfg", i + 1)); continue; } ConfigPolicyOverride configPolicyOverride = ((c == '+') ? ConfigPolicyOverride.ForceServerControlled : ConfigPolicyOverride.ForceClientControlled); if (result.TryGetValue(text3, out var value)) { diagnostics.Add((value == configPolicyOverride) ? string.Format("{0}:{1}: duplicate rule: {2}", "ConditionalConfigSync.SyncPolicy.cfg", i + 1, text3) : string.Format("{0}:{1}: conflicting rule overrides an earlier entry: {2}", "ConditionalConfigSync.SyncPolicy.cfg", i + 1, text3)); } result[text3] = configPolicyOverride; } } private static void ValidateHiddenPolicyFile(HashSet<string> result, List<string> diagnostics) { if (!File.Exists(HiddenConfigsPath)) { diagnostics.Add("File does not exist: " + HiddenConfigsPath); return; } string[] array = ReadAllLinesStable(HiddenConfigsPath); for (int i = 0; i < array.Length; i++) { string text = StripPolicyComment(array[i]).Trim(); if (text.Length != 0 && !result.Add(text)) { diagnostics.Add(string.Format("{0}:{1}: duplicate rule: {2}", "ConditionalConfigSync.HiddenConfigs.cfg", i + 1, text)); } } } private static ConfigPolicyOverride ResolvePolicyOverride(IReadOnlyDictionary<string, ConfigPolicyOverride> policy, string exactKey, string sectionKey, out string? matchedKey) { if (policy.TryGetValue(exactKey, out var value)) { matchedKey = exactKey; return value; } if (policy.TryGetValue(sectionKey, out var value2)) { matchedKey = sectionKey; return value2; } matchedKey = null; return ConfigPolicyOverride.Default; } private static string WritePolicyDumpFile() { EnsureConfigDirectory(); string text = Path.Combine(ConfigDirectoryPath, "ConditionalConfigSync.PolicyDump.txt"); List<string> list = new List<string> { "# ConditionalConfigSync policy identifiers", "# Copy an identifier into SyncPolicy.cfg and prefix it with '+' or '-'.", "# Copy an identifier into HiddenConfigs.cfg without a prefix.", "# To target a whole section, copy the section identifier shown after '# Section:'.", "" }; foreach (ConditionalConfigSync item in configSyncs.OrderBy<ConditionalConfigSync, string>((ConditionalConfigSync sync) => sync.Name, StringComparer.OrdinalIgnoreCase)) { string text2 = null; foreach (OwnConfigEntryBase item2 in item.allConfigs.OrderBy<OwnConfigEntryBase, string>((OwnConfigEntryBase entry) => entry.BaseConfig.Definition.Section, StringComparer.OrdinalIgnoreCase).ThenBy<OwnConfigEntryBase, string>((OwnConfigEntryBase entry) => entry.BaseConfig.Definition.Key, StringComparer.OrdinalIgnoreCase)) { string policySectionKey = item.GetPolicySectionKey(item2); if (!string.Equals(text2, policySectionKey, StringComparison.OrdinalIgnoreCase)) { if (text2 != null) { list.Add(""); } list.Add("# Section: " + policySectionKey); text2 = policySectionKey; } list.Add(item.GetPolicyKey(item2)); list.Add(string.Format("Policy: {0}; Default: {1}", item2.SyncMode, GetDefaultServerControlled(item2) ? "ServerControlled" : "ClientControlled")); } list.Add(""); } File.WriteAllLines(text, list); return text; } private static object HandlePolicyConsoleCommand(ConsoleEventArgs args) { string text = GameReflection.ConsoleArg(args, 0).Trim().ToLowerInvariant(); if (!GameReflection.HasZNet || !GameReflection.IsServer()) { return "This command is available only on the server."; } switch (text) { case "conditionalconfigsync_status": WritePolicyStatus(args); return true; case "conditionalconfigsync_policy_reload": { bool flag = LoadPolicyFiles(createIfMissing: true, quiet: false, "console command"); AddConsoleLine(args, flag ? "ConditionalConfigSync policy reloaded." : "ConditionalConfigSync policy reload failed. See the server log."); return flag; } case "conditionalconfigsync_policy_validate": { int syncRuleCount; int hiddenRuleCount; List<string> list = ValidatePolicyFiles(out syncRuleCount, out hiddenRuleCount); AddConsoleLine(args, $"ConditionalConfigSync policy validation: syncR
ConditionalConfigSync.Plugin.dll
Decompiled 19 hours agousing System; using System.ComponentModel; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("Conditional Config Sync Plugin")] [assembly: AssemblyDescription("BepInEx bootstrap for the Conditional Config Sync package.")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("shudnal")] [assembly: AssemblyProduct("Conditional Config Sync")] [assembly: AssemblyCopyright("Copyright © shudnal 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("815dc453-badf-4351-b8aa-92321836fcb3")] [assembly: AssemblyFileVersion("1.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/shudnal/ConditionalConfigSync")] [assembly: AssemblyMetadata("License", "Unlicense")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.0.0.0")] [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 ConditionalConfigSync { [BepInPlugin("_shudnal.ConditionalConfigSync", "Conditional Config Sync", "1.0.0")] [Description("Standalone BepInEx bootstrap that initializes ConditionalConfigSync.dll.")] public sealed class ConditionalConfigSyncPlugin : BaseUnityPlugin { [Description("BepInEx hard-dependency identifier used by mods that consume ConditionalConfigSync.")] public const string PluginGuid = "_shudnal.ConditionalConfigSync"; [Description("Display name of the standalone BepInEx plugin.")] public const string PluginName = "Conditional Config Sync"; [Description("Package version sourced from PluginInfo.PluginVersion.")] public const string PluginVersion = "1.0.0"; private const string PluginAssemblyName = "ConditionalConfigSync.Plugin"; private const string CoreAssemblyName = "ConditionalConfigSync"; private void Awake() { string text = typeof(ConditionalConfigSyncPlugin).Assembly.GetName().Name ?? string.Empty; if (!string.Equals(text, "ConditionalConfigSync.Plugin", StringComparison.Ordinal)) { ((BaseUnityPlugin)this).Logger.LogFatal((object)("Embedded ConditionalConfigSync bootstrap detected inside assembly '" + text + "'. The bootstrap plugin must only run from ConditionalConfigSync.Plugin.dll.")); ((Behaviour)this).enabled = false; return; } string text2 = typeof(ConditionalConfigSync).Assembly.GetName().Name ?? string.Empty; if (!string.Equals(text2, "ConditionalConfigSync", StringComparison.Ordinal)) { ((BaseUnityPlugin)this).Logger.LogFatal((object)("ConditionalConfigSync core was resolved from unsupported assembly '" + text2 + "'. Remove embedded copies and install the standalone ConditionalConfigSync mod.")); ((Behaviour)this).enabled = false; } else { ConditionalConfigSync.InitializeRuntime(); } } private void OnDestroy() { ConditionalConfigSync.ShutdownRuntime(); } } }