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.4
ConditionalConfigSync.dll
Decompiled a month 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.Text; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.UI; [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.4")] [assembly: AssemblyInformationalVersion("1.0.4")] [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 ConfigEntryOnSettingChangedPatch { internal static bool Prefix(ConfigEntryBase __instance) { OwnConfigEntryBase configData = GetConfigData(__instance); if (configData != null) { ConditionalConfigSync owningConfigSync = GetOwningConfigSync(configData); if (owningConfigSync != null && !owningConfigSync.configsBeingApplied.Contains(__instance) && !IsWritableConfig(configData)) { owningConfigSync.RestoreRejectedConfigChange(__instance, configData, owningConfigSync.GetWriteRejectionReason(configData)); return false; } } return true; } } internal static class ConfigEntryGetSerializedValuePatch { internal static bool Prefix(ConfigEntryBase __instance, ref string __result) { OwnConfigEntryBase configData = GetConfigData(__instance); if (configData == null || IsWritableConfig(configData)) { return true; } object obj = (configData.HasLocalBaseValue ? configData.LocalBaseValue : (configData.HasLastAcceptedValue ? configData.LastAcceptedValue : __instance.BoxedValue)); __result = TomlTypeConverter.ConvertToString(obj, __instance.SettingType); return false; } } internal static class ConfigEntrySetSerializedValuePatch { internal static bool Prefix(ConfigEntryBase __instance, string value) { OwnConfigEntryBase configData = GetConfigData(__instance); if (configData == null || IsWritableConfig(configData)) { 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>(); public readonly Dictionary<OwnConfigEntryBase, ReceivedConfigState> clientConfigStateClaims = new Dictionary<OwnConfigEntryBase, ReceivedConfigState>(); public int? capabilities; public string? rejectionReason; public int entryCount; public void Reject(string reason) { if (rejectionReason == null) { rejectionReason = reason; } } } 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 int? capabilities; public static PackageEntry ServerVersion(string version) { return new PackageEntry { kind = PackageEntryKind.ServerVersion, value = version }; } public static PackageEntry LockExempt(bool value, int? capabilities = null) { return new PackageEntry { kind = PackageEntryKind.LockExempt, value = value, capabilities = capabilities }; } 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(), (MonoBehaviour?)(object)__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), (MonoBehaviour?)(object)__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; } } private static bool ShouldBufferInitialPackage(int methodHash) { return methodHash == GameReflection.StableHash("PeerInfo") || methodHash == GameReflection.StableHash("PlayerList") || methodHash == GameReflection.StableHash("AdminList") || methodHash == GameReflection.StableHash("RoutedRPC") || methodHash == GameReflection.StableHash("ZDOData"); } public void Send(ZPackage pkg) { int position = GameReflection.PackageGetPos(pkg); GameReflection.PackageSetPos(pkg, 0); int methodHash = GameReflection.PackageReadInt(pkg); GameReflection.PackageSetPos(pkg, position); if (!finished && ShouldBufferInitialPackage(methodHash)) { ZPackage val = GameReflection.NewPackage(GameReflection.PackageGetArray(pkg)); GameReflection.PackageSetPos(val, position); Package.Add(val); } else { 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(), (MonoBehaviour?)(object)__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), (MonoBehaviour?)(object)__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 non-admin clients to publish changes. Disabled by default.")] public bool AllowClientConfigUpdatesWhenUnlocked = false; 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 const string PolicyChangeRpcSuffix = " ConditionalConfigSync Policy Change"; private const int MaxPolicyChangeRequestSize = 4096; private const int MaxPolicyIdentifierLength = 1024; private const int PolicyChangeCapability = 1; private bool remotePolicyChangeSupported; 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 readonly Dictionary<long, long> authoritativeCorrectionTimes = new Dictionary<long, long>(); private static readonly long AuthoritativeCorrectionIntervalTicks = TimeSpan.FromSeconds(1.0).Ticks; 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; private bool ServerLockEnabled => forceConfigLocking ?? (lockedConfig != null && ((IConvertible)lockedConfig.BaseConfig.BoxedValue).ToInt32(CultureInfo.InvariantCulture) != 0); [Description("Effective local lock state. Unlocked non-admin writes still require explicit mod opt-in.")] public bool IsLocked { get { return ServerLockEnabled && !lockExempt; } 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 }; syncedEntry.StoreLastAcceptedValue(((ConfigEntryBase)configEntry).BoxedValue); 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.IsPolicyStateInitialized = 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.SyncMode = ConfigSyncMode.AlwaysServerControlled; lockedConfig.SynchronizedConfig = true; lockedConfig.IsServerControlled = true; lockedConfig.IsPolicyStateInitialized = IsSourceOfTruth && isServer && GameReflection.HasZNet && policySupportInitialized; lockedConfig.StoreLastAcceptedValue(((ConfigEntryBase)lockingConfig).BoxedValue); 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); } internal static bool IsWritableConfig(OwnConfigEntryBase config) { ConditionalConfigSync conditionalConfigSync = configSyncs.FirstOrDefault((ConditionalConfigSync cs) => cs.allConfigs.Contains(config)); if (conditionalConfigSync == null) { return true; } if (conditionalConfigSync.IsSourceOfTruth || !config.IsServerControlled) { return true; } if (!conditionalConfigSync.InitialSyncDone) { return false; } if (config == conditionalConfigSync.lockedConfig) { return conditionalConfigSync.IsAdmin; } if (conditionalConfigSync.IsAdmin) { return true; } if (conditionalConfigSync.ServerLockEnabled) { return false; } return conditionalConfigSync.AllowClientConfigUpdatesWhenUnlocked; } private string GetWriteRejectionReason(OwnConfigEntryBase config) { if (!InitialSyncDone) { return "initial server synchronization is not complete"; } if (config == lockedConfig) { return "the protected locking config requires administrator access"; } if (ServerLockEnabled) { return "the server configuration is locked"; } if (!AllowClientConfigUpdatesWhenUnlocked) { return "unlocked client updates are disabled by the mod"; } return "the local side is not authorized to change this server-controlled config"; } private void ServerLockedSettingChanged() { RaiseLockStateChangedIfNeeded(); DebugLog(ConditionalConfigSyncDebugLevel.Verbose, "Lock", $"Re-evaluating read-only state: serverLock={ServerLockEnabled}, effectiveLock={IsLocked}, admin={IsAdmin}, sourceOfTruth={IsSourceOfTruth}, initialSync={InitialSyncDone}, unlockedClientUpdates={AllowClientConfigUpdatesWhenUnlocked}"); foreach (OwnConfigEntryBase allConfig in allConfigs) { ConfigurationManagerAttributes configAttribute = GetConfigAttribute<ConfigurationManagerAttributes>(allConfig.BaseConfig); configAttribute.ReadOnly = !IsWritableConfig(allConfig); configAttribute.Browsable = !allConfig.IsHidden; } } private void RestoreRejectedConfigChange(ConfigEntryBase configEntry, OwnConfigEntryBase syncedEntry, string reason) { object boxedValue = (syncedEntry.HasServerValue ? syncedEntry.ServerValue : (syncedEntry.HasLastAcceptedValue ? syncedEntry.LastAcceptedValue : (syncedEntry.HasLocalBaseValue ? syncedEntry.LocalBaseValue : configEntry.DefaultValue))); ConfigFile configFile = configEntry.ConfigFile; bool saveOnConfigSet = configFile.SaveOnConfigSet; configFile.SaveOnConfigSet = false; configsBeingApplied.Add(configEntry); try { configEntry.BoxedValue = boxedValue; syncedEntry.StoreLastAcceptedValue(configEntry.BoxedValue); } catch (Exception arg) { ConfigDefinition definition = configEntry.Definition; DebugWarning("ConfigUpdate", $"Failed to restore protected config {definition.Section} -> {definition.Key} after a rejected local change. Error: {arg}"); return; } finally { configsBeingApplied.Remove(configEntry); configFile.SaveOnConfigSet = saveOnConfigSet; } try { configFile.Save(); } catch (Exception ex) { DebugWarning("ConfigUpdate", $"Failed to save local fallback after restoring {configEntry.Definition}: {ex.Message}"); } DebugWarning("ConfigUpdate", "Restored protected config " + configEntry.Definition.Section + " -> " + configEntry.Definition.Key + " after a rejected local change: " + reason); } private void ResetConfigsFromServer() { remotePolicyChangeSupported = false; 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.StoreLastAcceptedValue(item.BaseConfig.BoxedValue); 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.IsPolicyStateInitialized = false; allConfig.StoreLastAcceptedValue(allConfig.BaseConfig.BoxedValue); 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.StoreLastAcceptedValue(item3.BoxedValue); 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; ServerLockedSettingChanged(); 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)); } internal static void VersionErrorLog(string area, string modName, string message) { LogSource.LogError((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.IsPolicyStateInitialized = 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; customValue.Key.StoreLastAcceptedValue(customValue.Key.BoxedValue); } 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) { if (configs.capabilities.HasValue) { remotePolicyChangeSupported = (configs.capabilities.Value & 1) != 0; } 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; config.StoreLastAcceptedValue(config.BaseConfig.BoxedValue); 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, bool strictClientUpdate = false) { 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); HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); int num = (parsedConfigs.entryCount = 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) { if (strictClientUpdate) { parsedConfigs.Reject($"entry {num2 + 1} exceeds the {20971520}-byte entry limit"); } else { 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); string item = "config\n" + text + "\n" + text2; if (!hashSet.Add(item)) { if (strictClientUpdate) { parsedConfigs.Reject("duplicate config entry " + text + " -> " + text2); } break; } if (dictionary.TryGetValue(text + "\n" + text2, out var value)) { try { parsedConfigs.configValues[value] = TomlTypeConverter.ConvertToValue(text3, value.BaseConfig.SettingType); } catch (Exception ex) { if (strictClientUpdate) { parsedConfigs.Reject("config " + text + " -> " + text2 + " could not be parsed: " + ex.Message); } else { DebugWarning("Read", $"Config value of setting \"{value.BaseConfig.Definition}\" could not be parsed and will be ignored. Reason: {ex.Message}"); } } } else if (strictClientUpdate) { parsedConfigs.Reject("unknown config entry " + text + " -> " + text2); } else { DebugWarning("Read", "Received unknown config entry " + text + "/" + text2 + ". This may happen if client and server versions of the mod do not match."); } if (strictClientUpdate && GameReflection.PackageGetPos(package2) != GameReflection.PackageSize(package2)) { parsedConfigs.Reject("config entry " + text + " -> " + text2 + " contains trailing data"); } break; } case PackageEntryKind.ConfigState: { string text4 = GameReflection.PackageReadString(package2); string text5 = GameReflection.PackageReadString(package2); bool serverControlled = GameReflection.PackageReadBool(package2); bool hidden = GameReflection.PackageReadBool(package2); string item2 = "state\n" + text4 + "\n" + text5; if (!hashSet.Add(item2)) { if (strictClientUpdate) { parsedConfigs.Reject("duplicate config state " + text4 + " -> " + text5); } break; } if (dictionary.TryGetValue(text4 + "\n" + text5, out var value2)) { if (strictClientUpdate) { parsedConfigs.clientConfigStateClaims[value2] = new ReceivedConfigState(serverControlled, hidden); } else { parsedConfigs.configStates[value2] = new ReceivedConfigState(serverControlled, hidden); } } else if (strictClientUpdate) { parsedConfigs.Reject("unknown config state " + text4 + " -> " + text5); } else { DebugWarning("Read", "Received unknown config state " + text4 + "/" + text5 + ". This may happen if client and server versions of the mod do not match."); } if (strictClientUpdate && GameReflection.PackageGetPos(package2) != GameReflection.PackageSize(package2)) { parsedConfigs.Reject("config state " + text4 + " -> " + text5 + " contains trailing data"); } break; } case PackageEntryKind.CustomValue: { string text7 = GameReflection.PackageReadString(package2); string text8 = GameReflection.PackageReadString(package2); string item3 = "custom\n" + text7; if (!hashSet.Add(item3)) { if (strictClientUpdate) { parsedConfigs.Reject("duplicate custom synced value " + text7); } break; } if (!dictionary2.TryGetValue(text7, out var value3)) { if (strictClientUpdate) { parsedConfigs.Reject("unknown custom synced value " + text7); } else { 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) { if (strictClientUpdate) { parsedConfigs.Reject("custom synced value " + text7 + " has type " + text8 + ", expected " + zPackageTypeString); } else { DebugWarning("Read", "Got unexpected type " + text8 + " for custom synced value " + text7 + ", expecting " + zPackageTypeString); } break; } try { parsedConfigs.customValues[value3] = ReadCustomValueFromPackage(package2, value3.Type); if (strictClientUpdate && GameReflection.PackageGetPos(package2) != GameReflection.PackageSize(package2)) { parsedConfigs.Reject("custom synced value " + text7 + " contains trailing data"); } } catch (InvalidDeserializationTypeException ex2) { if (strictClientUpdate) { parsedConfigs.Reject("custom synced value " + text7 + " has unexpected internal type " + ex2.received + " for " + ex2.field + ", expected " + ex2.expected); } else { 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 ex3) { if (strictClientUpdate) { parsedConfigs.Reject("custom synced value " + text7 + " could not be deserialized: " + ex3.Message); } else { DebugWarning("Read", $"Could not deserialize custom synced value {text7}: {ex3}"); } } break; } case PackageEntryKind.ServerVersion: { string text6 = GameReflection.PackageReadString(package2); if (strictClientUpdate) { parsedConfigs.Reject("client update contains prohibited ServerVersion metadata"); } else if (receivedFromServer && text6 != CurrentVersion) { DebugWarning("Version", "Received server version is not equal: server version=" + text6 + ", local version=" + (CurrentVersion ?? "unknown")); } break; } case PackageEntryKind.LockExempt: { bool flag = GameReflection.PackageReadBool(package2); if (strictClientUpdate) { parsedConfigs.Reject("client update contains prohibited LockExempt metadata"); } else if (receivedFromServer) { lockExempt = flag; if (GameReflection.PackageSize(package2) - GameReflection.PackageGetPos(package2) >= 4) { parsedConfigs.capabilities = GameReflection.PackageReadInt(package2); } } break; } default: if (strictClientUpdate) { parsedConfigs.Reject($"client update contains unknown entry kind {(byte)packageEntryKind}"); } else { DebugWarning("Read", $"Received unknown config entry kind {(byte)packageEntryKind}"); } break; } } if (strictClientUpdate) { foreach (KeyValuePair<OwnConfigEntryBase, ReceivedConfigState> clientConfigStateClaim in parsedConfigs.clientConfigStateClaims) { ConfigDefinition definition = clientConfigStateClaim.Key.BaseConfig.Definition; if (!parsedConfigs.configValues.ContainsKey(clientConfigStateClaim.Key)) { parsedConfigs.Reject("client update contains config state without a value for " + definition.Section + " -> " + definition.Key); continue; } bool flag2 = ComputeServerControlled(clientConfigStateClaim.Key); bool flag3 = ComputeHidden(clientConfigStateClaim.Key); if (clientConfigStateClaim.Value.ServerControlled != flag2 || clientConfigStateClaim.Value.Hidden != flag3) { parsedConfigs.Reject("client config state for " + definition.Section + " -> " + definition.Key + " does not match the server's effective policy"); } } if (GameReflection.PackageGetPos(package) != GameReflection.PackageSize(package)) { parsedConfigs.Reject("client update package contains trailing data"); } } 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, bool includeConfigStates = true) { 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)); int num = (includeConfigStates ? list.Count : 0); GameReflection.PackageWrite(val, list2.Count + num + list3.Count + list4.Count); foreach (PackageEntry item in list4) { AddEntryToPackage(val, item); } if (includeConfigStates) { 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); if (entry.capabilities.HasValue) { GameReflection.PackageWrite(package2, entry.capabilities.Value); } 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)$"[SyncPolicy] 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.IsPolicyStateInitialized; 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.IsPolicyStateInitialized = 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, "Hidde
ConditionalConfigSync.Plugin.dll
Decompiled a month 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.4")] [assembly: AssemblyInformationalVersion("1.0.4")] [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.4.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.4")] [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.4"; 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(); } } }