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 MushroomSync v1.0.0
MushroomSync.dll
Decompiled 3 weeks agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("MushroomSync")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Shared server-authoritative sync for the Mushroom mods.")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+3c15a0cc54c8b70d1d4aad090ba6cdc3ca225dc9")] [assembly: AssemblyProduct("Mushroom Sync")] [assembly: AssemblyTitle("MushroomSync")] [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.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace MushroomSync { internal static class ChannelRegistry { private static readonly List<SyncChannel> Channels = new List<SyncChannel>(); private static readonly object Gate = new object(); private static ManualLogSource _log; private static bool _patched; internal static void Initialize(Harmony harmony, ManualLogSource log) { _log = log; if (!_patched) { Patch(harmony, AccessTools.Method(typeof(ZNet), "OnNewConnection", new Type[1] { typeof(ZNetPeer) }, (Type[])null), "ZNetOnNewConnectionPostfix", null, required: true); Patch(harmony, AccessTools.Method(typeof(ZNet), "RPC_PeerInfo", new Type[2] { typeof(ZRpc), typeof(ZPackage) }, (Type[])null), "ZNetRpcPeerInfoPostfix", null, required: true); Patch(harmony, AccessTools.Method(typeof(ZNet), "Disconnect", new Type[1] { typeof(ZNetPeer) }, (Type[])null), "ZNetDisconnectPostfix"); Patch(harmony, AccessTools.Method(typeof(ZNet), "OnDestroy", Type.EmptyTypes, (Type[])null), null, "ZNetOnDestroyPrefix"); _patched = true; } } internal static void Add(SyncChannel channel) { lock (Gate) { foreach (SyncChannel channel2 in Channels) { if (string.Equals(channel2.Id, channel.Id, StringComparison.Ordinal)) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("Two sync channels share the id '" + channel.Id + "'. The second is ignored; give each mod its own id.")); } return; } } Channels.Add(channel); } } private static SyncChannel[] Snapshot() { lock (Gate) { return Channels.ToArray(); } } private static void Patch(Harmony harmony, MethodInfo method, string postfix = null, string prefix = null, bool required = false) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) if (method == null) { string text = postfix ?? prefix; if (required) { ManualLogSource log = _log; if (log != null) { log.LogError((object)("MushroomSync: could not find the ZNet method for " + text + ". Sync will not work.")); } } else { ManualLogSource log2 = _log; if (log2 != null) { log2.LogWarning((object)("MushroomSync: could not find the ZNet method for " + text + ".")); } } } else { harmony.Patch((MethodBase)method, (prefix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(ChannelRegistry), prefix, (Type[])null), (postfix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(ChannelRegistry), postfix, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } private static IEnumerator DeferFrames(int frameCount, Action action) { for (int i = 0; i < frameCount; i++) { yield return null; } try { action(); } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("MushroomSync: deferred action failed: " + ex.Message)); } } } public static void ZNetOnNewConnectionPostfix(ZNetPeer peer) { if (peer != null && peer.m_rpc != null) { SyncChannel[] array = Snapshot(); for (int i = 0; i < array.Length; i++) { array[i].RegisterOn(peer.m_rpc); } } } public static void ZNetRpcPeerInfoPostfix(ZNet __instance, ZRpc rpc) { if ((Object)(object)__instance == (Object)null || rpc == null) { return; } SyncChannel[] channels = Snapshot(); if (channels.Length == 0) { return; } if (__instance.IsServer()) { ((MonoBehaviour)__instance).StartCoroutine(DeferFrames(channels[0].DeferredFrames, delegate { SyncChannel[] array = channels; for (int i = 0; i < array.Length; i++) { array[i].SendTo(rpc); } })); } else { if (__instance.GetServerRPC() != rpc) { return; } ((MonoBehaviour)__instance).StartCoroutine(DeferFrames(channels[0].DeferredFrames, delegate { SyncChannel[] array = channels; for (int i = 0; i < array.Length; i++) { array[i].RequestFromServer(); } })); } } public static void ZNetDisconnectPostfix(ZNet __instance, ZNetPeer peer) { if (!((Object)(object)__instance == (Object)null) && !__instance.IsServer() && peer != null && peer.m_server) { SyncChannel[] array = Snapshot(); for (int i = 0; i < array.Length; i++) { array[i].ClearClientState("disconnect"); } } } public static void ZNetOnDestroyPrefix(ZNet __instance) { if (!((Object)(object)__instance == (Object)null) && !__instance.IsServer()) { SyncChannel[] array = Snapshot(); for (int i = 0; i < array.Length; i++) { array[i].ClearClientState("network shutdown"); } } } } public sealed class ConfigSync { private static readonly string PathSeparator = '\u001f'.ToString(); private readonly Dictionary<string, ConfigEntryBase> _entries = new Dictionary<string, ConfigEntryBase>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<ConfigEntryBase, object> _syncedValues = new Dictionary<ConfigEntryBase, object>(); private readonly HashSet<ConfigEntryBase> _excluded = new HashSet<ConfigEntryBase>(); private readonly HashSet<ConfigFile> _watchedFiles = new HashSet<ConfigFile>(); private bool _hasHostValues; private readonly SyncChannel _channel; private readonly ManualLogSource _log; private readonly string _id; private Action _applied; private Action<string> _notify; public bool ClientSyncActive => _channel.ClientSyncActive; private ConfigSync(string id, string version, ManualLogSource log) { _id = id; _log = log; _channel = SyncChannel.Create(id + ".Config", version, log); _channel.WritePayload = WritePayload; _channel.ReadPayload = ReadPayload; _channel.Cleared = OnCleared; } public static bool IsServerAuthority() { return SyncChannel.IsServerAuthority(); } public static ConfigSync Create(string id, string version, ManualLogSource log) { return new ConfigSync(id, version, log); } public ConfigSync Protecting(ConfigFile file) { ConfigValueOverlay.Protect(file); return this; } public ConfigSync OnApplied(Action applied) { _applied = applied; return this; } public ConfigSync Notifying(Action<string> notify) { _notify = notify; return this; } public ConfigSync GatedBy(Func<bool> gate) { _channel.SendGate = gate; return this; } public ConfigSync AcceptedWhen(Func<bool> gate) { _channel.AcceptGate = gate; return this; } public ConfigSync Start() { _channel.Start(); return this; } public ConfigSync Register(ConfigEntryBase entry) { if (entry == null) { return this; } string key = BuildPath(entry.Definition.Section, entry.Definition.Key); if (_entries.ContainsKey(key)) { return this; } _entries[key] = entry; ConfigValueOverlay.Claim(entry, this); return this; } public ConfigSync Register(params ConfigEntryBase[] entries) { if (entries != null) { foreach (ConfigEntryBase entry in entries) { Register(entry); } } return this; } public ConfigSync WatchForChanges(ConfigFile file) { if (file == null || !_watchedFiles.Add(file)) { return this; } file.SettingChanged += OnSettingChanged; return this; } public ConfigSync Exclude(ConfigEntryBase entry) { if (entry == null) { return this; } _excluded.Add(entry); ConfigValueOverlay.Release(entry); _entries.Remove(BuildPath(entry.Definition.Section, entry.Definition.Key)); return this; } public bool TryGetSyncedValue<T>(ConfigEntry<T> entry, out T value) { if (_channel.ClientSyncActive && entry != null && _syncedValues.TryGetValue((ConfigEntryBase)(object)entry, out var value2) && value2 is T) { value = (T)value2; return true; } value = default(T); return false; } public void Broadcast() { if (IsServerAuthority()) { _channel.Broadcast(); } } private void OnSettingChanged(object sender, SettingChangedEventArgs args) { if (args != null && args.ChangedSetting != null) { string key = BuildPath(args.ChangedSetting.Definition.Section, args.ChangedSetting.Definition.Key); if (_entries.ContainsKey(key)) { Broadcast(); } } } private static string BuildPath(string section, string key) { return (section ?? string.Empty) + PathSeparator + (key ?? string.Empty); } private bool ShouldSync(ConfigEntryBase entry) { if (entry != null) { return !_excluded.Contains(entry); } return false; } private void WritePayload(ZPackage payload) { List<ConfigEntryBase> list = new List<ConfigEntryBase>(); foreach (ConfigEntryBase value in _entries.Values) { if (ShouldSync(value)) { list.Add(value); } } list.Sort(CompareEntries); payload.Write(list.Count); foreach (ConfigEntryBase item in list) { payload.Write(item.Definition.Section ?? string.Empty); payload.Write(item.Definition.Key ?? string.Empty); payload.Write(ConfigValueOverlay.ReadLocalSerialized(item)); } } private static int CompareEntries(ConfigEntryBase a, ConfigEntryBase b) { int num = StringComparer.OrdinalIgnoreCase.Compare(a.Definition.Section, b.Definition.Section); if (num == 0) { return StringComparer.OrdinalIgnoreCase.Compare(a.Definition.Key, b.Definition.Key); } return num; } private void ReadPayload(ZPackage payload) { int num = SyncChannel.ReadCount(payload); Dictionary<ConfigEntryBase, object> dictionary = new Dictionary<ConfigEntryBase, object>(); int num2 = 0; for (int i = 0; i < num; i++) { string text = payload.ReadString(); string text2 = payload.ReadString(); string text3 = payload.ReadString(); object value2; if (!_entries.TryGetValue(BuildPath(text, text2), out var value) || !ShouldSync(value)) { num2++; } else if (!TryConvert(value, text3, out value2)) { _log.LogWarning((object)(_id + ": could not parse [" + text + "] " + text2 + " = '" + text3 + "' as " + value.SettingType.Name + "; keeping the local value.")); } else { dictionary[value] = value2; } } bool flag = !_hasHostValues; _syncedValues.Clear(); foreach (KeyValuePair<ConfigEntryBase, object> item in dictionary) { _syncedValues[item.Key] = item.Value; } _hasHostValues = true; RaiseApplied(); if (flag) { _log.LogInfo((object)(_id + ": using host configuration (" + dictionary.Count + " settings).")); Notify(_id + ": using host configuration."); } else { _log.LogInfo((object)(_id + ": host configuration updated (" + dictionary.Count + " settings).")); } if (num2 > 0) { _log.LogInfo((object)(_id + ": ignored " + num2 + " setting(s) this build does not have.")); } } private void OnCleared(string reason) { _syncedValues.Clear(); _hasHostValues = false; RaiseApplied(); _log.LogInfo((object)(_id + ": host configuration dropped (" + reason + "); using local settings.")); if (reason == "version mismatch" || reason == "invalid payload") { Notify(_id + ": host config unavailable (" + reason + "); using your local settings."); } } private static bool TryConvert(ConfigEntryBase entry, string serialized, out object value) { try { if (entry.SettingType.IsEnum) { value = Enum.Parse(entry.SettingType, serialized ?? string.Empty, ignoreCase: true); return true; } value = TomlTypeConverter.ConvertToValue(serialized, entry.SettingType); return value != null; } catch (Exception) { value = null; return false; } } private void RaiseApplied() { if (_applied == null) { return; } try { _applied(); } catch (Exception ex) { _log.LogWarning((object)(_id + ": OnApplied handler failed: " + ex.Message)); } } private void Notify(string message) { if (_notify == null) { return; } try { _notify(message); } catch (Exception ex) { _log.LogWarning((object)(_id + ": notify handler failed: " + ex.Message)); } } } internal static class ConfigValueOverlay { private static readonly Dictionary<ConfigEntryBase, ConfigSync> Owners = new Dictionary<ConfigEntryBase, ConfigSync>(); private static readonly HashSet<Type> PatchedTypes = new HashSet<Type>(); private static readonly HashSet<ConfigFile> ProtectedFiles = new HashSet<ConfigFile>(); private static readonly object Gate = new object(); private static Harmony _harmony; private static ManualLogSource _log; [ThreadStatic] private static int _readLocalDepth; internal static void Initialize(Harmony harmony, ManualLogSource log) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Expected O, but got Unknown //IL_0074: Expected O, but got Unknown _harmony = harmony; _log = log; MethodInfo methodInfo = AccessTools.Method(typeof(ConfigFile), "Save", Type.EmptyTypes, (Type[])null); if (methodInfo == null) { _log.LogWarning((object)"MushroomSync: ConfigFile.Save not found; a synced client that saves its config could persist host values locally."); } else { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(ConfigValueOverlay), "ConfigFileSavePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(ConfigValueOverlay), "ConfigFileSaveFinalizer", (Type[])null), (HarmonyMethod)null); } } internal static void Claim(ConfigEntryBase entry, ConfigSync owner) { lock (Gate) { if (Owners.ContainsKey(entry)) { return; } Owners[entry] = owner; } EnsurePatchedFor(entry.SettingType); } internal static void Release(ConfigEntryBase entry) { lock (Gate) { Owners.Remove(entry); } } internal static void Protect(ConfigFile file) { if (file == null) { return; } lock (Gate) { ProtectedFiles.Add(file); } } internal static string ReadLocalSerialized(ConfigEntryBase entry) { _readLocalDepth++; try { return entry.GetSerializedValue() ?? string.Empty; } finally { _readLocalDepth--; } } private static void EnsurePatchedFor(Type settingType) { //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Expected O, but got Unknown if (settingType == null || _harmony == null) { return; } lock (Gate) { if (!PatchedTypes.Add(settingType)) { return; } } try { MethodInfo methodInfo = typeof(ConfigEntry<>).MakeGenericType(settingType).GetProperty("Value", BindingFlags.Instance | BindingFlags.Public)?.GetGetMethod(); if (methodInfo == null) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("MushroomSync: no Value getter on ConfigEntry<" + settingType.Name + ">.")); } } else { MethodInfo methodInfo2 = AccessTools.Method(typeof(ConfigValueOverlay), "ValueGetterPostfix", (Type[])null, (Type[])null).MakeGenericMethod(settingType); _harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } catch (Exception ex) { lock (Gate) { PatchedTypes.Remove(settingType); } ManualLogSource log2 = _log; if (log2 != null) { log2.LogWarning((object)("MushroomSync: could not overlay ConfigEntry<" + settingType.Name + ">.Value: " + ex.Message + ". TryGetSyncedValue still works for it.")); } } } public static void ValueGetterPostfix<T>(ConfigEntry<T> __instance, ref T __result) { if (_readLocalDepth > 0 || __instance == null) { return; } ConfigSync value; lock (Gate) { if (!Owners.TryGetValue((ConfigEntryBase)(object)__instance, out value)) { return; } } if (value.TryGetSyncedValue<T>(__instance, out var value2)) { __result = value2; } } public static void ConfigFileSavePrefix(ConfigFile __instance, ref bool __state) { __state = false; lock (Gate) { if (!ProtectedFiles.Contains(__instance)) { return; } } _readLocalDepth++; __state = true; } public static Exception ConfigFileSaveFinalizer(Exception __exception, bool __state) { if (__state && _readLocalDepth > 0) { _readLocalDepth--; } return __exception; } } [BepInPlugin("mushroom.sync", "Mushroom Sync", "1.0.0")] public class MushroomSyncPlugin : BaseUnityPlugin { public const string PluginGuid = "mushroom.sync"; public const string PluginName = "Mushroom Sync"; public const string PluginVersion = "1.0.0"; private Harmony _harmony; internal static ManualLogSource Log { get; private set; } private void Awake() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; _harmony = new Harmony("mushroom.sync"); ChannelRegistry.Initialize(_harmony, ((BaseUnityPlugin)this).Logger); ConfigValueOverlay.Initialize(_harmony, ((BaseUnityPlugin)this).Logger); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Mushroom Sync 1.0.0 loaded."); } private void OnDestroy() { if (_harmony != null) { _harmony.UnpatchSelf(); } } } public sealed class SyncChannel { public const int ProtocolVersion = 2; private const int DeferFrames = 2; private const int MaxEntries = 10000; private readonly string _id; private readonly string _version; private readonly ManualLogSource _log; private bool _clientSyncActive; private bool _started; public string Id => _id; public string RpcSync => _id + ".Sync"; public string RpcSyncRequest => _id + ".SyncRequest"; public bool ClientSyncActive => _clientSyncActive; public Action<ZPackage> WritePayload { get; set; } public Action<ZPackage> ReadPayload { get; set; } public Action<string> Cleared { get; set; } public Func<bool> SendGate { get; set; } public Func<bool> AcceptGate { get; set; } internal int DeferredFrames => 2; private SyncChannel(string id, string version, ManualLogSource log) { _id = id; _version = version ?? string.Empty; _log = log; } public static bool IsServerAuthority() { ZNet instance = ZNet.instance; if (!((Object)(object)instance == (Object)null)) { return instance.IsServer(); } return true; } public static SyncChannel Create(string id, string version, ManualLogSource log) { if (string.IsNullOrEmpty(id)) { throw new ArgumentException("A sync channel needs an id.", "id"); } if (log == null) { throw new ArgumentNullException("log"); } return new SyncChannel(id, version, log); } public SyncChannel Start() { if (_started) { return this; } _started = true; ChannelRegistry.Add(this); return this; } public void Broadcast() { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || !instance.IsServer()) { return; } foreach (ZNetPeer connectedPeer in instance.GetConnectedPeers()) { if (connectedPeer != null && connectedPeer.IsReady() && connectedPeer.m_rpc != null) { SendTo(connectedPeer.m_rpc); } } } public void ClearClientState(string reason) { if (_clientSyncActive) { _clientSyncActive = false; InvokeCleared(reason); } } internal void RegisterOn(ZRpc rpc) { try { rpc.Register<ZPackage>(RpcSync, (Action<ZRpc, ZPackage>)ReceiveSync); rpc.Register<ZPackage>(RpcSyncRequest, (Action<ZRpc, ZPackage>)ReceiveSyncRequest); } catch (Exception ex) { _log.LogWarning((object)(_id + ": RPC registration failed: " + ex.Message)); } } internal void SendTo(ZRpc rpc) { if (rpc == null || !rpc.IsConnected() || WritePayload == null || (SendGate != null && !SendGate())) { return; } try { rpc.Invoke(RpcSync, new object[1] { BuildEnvelope() }); } catch (Exception ex) { _log.LogWarning((object)(_id + ": send failed: " + ex.Message)); } } internal void RequestFromServer() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || instance.IsServer()) { return; } ZRpc serverRPC = instance.GetServerRPC(); if (serverRPC == null || !serverRPC.IsConnected()) { return; } try { ZPackage val = new ZPackage(); val.Write(2); val.Write(_version); serverRPC.Invoke(RpcSyncRequest, new object[1] { val }); } catch (Exception ex) { _log.LogWarning((object)(_id + ": sync request failed: " + ex.Message)); } } private ZPackage BuildEnvelope() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(2); val.Write(_version); ZPackage val2 = new ZPackage(); WritePayload(val2); val.WriteCompressed(val2); return val; } private void ReceiveSyncRequest(ZRpc rpc, ZPackage request) { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || !instance.IsServer() || rpc == null || request == null) { return; } try { int num = request.ReadInt(); string text = request.ReadString(); if (num != 2 || !string.Equals(text, _version, StringComparison.Ordinal)) { _log.LogWarning((object)(_id + ": version mismatch for a guest (guest " + text + " / protocol " + num + "; host " + _version + " / protocol " + 2 + "). Sending host values anyway.")); } } catch (Exception ex) { _log.LogWarning((object)(_id + ": sync request read failed: " + ex.Message)); } SendTo(rpc); } private void ReceiveSync(ZRpc rpc, ZPackage envelope) { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || instance.IsServer() || rpc == null || envelope == null || instance.GetServerRPC() != rpc || ReadPayload == null) { return; } if (AcceptGate != null && !AcceptGate()) { ClearClientState("declined locally"); return; } try { int num = envelope.ReadInt(); string text = envelope.ReadString(); if (num != 2 || !string.Equals(text, _version, StringComparison.Ordinal)) { ClearClientState("version mismatch"); _log.LogWarning((object)(_id + ": host mod version differs (" + text + " vs " + _version + "). Using local values.")); } else { ZPackage obj = envelope.ReadCompressedPackage(); _clientSyncActive = true; ReadPayload(obj); } } catch (Exception ex) { _clientSyncActive = false; InvokeCleared("invalid payload"); _log.LogWarning((object)(_id + ": sync failed: " + ex.Message)); } } private void InvokeCleared(string reason) { if (Cleared == null) { return; } try { Cleared(reason); } catch (Exception ex) { _log.LogWarning((object)(_id + ": cleared handler failed: " + ex.Message)); } } public static int ReadCount(ZPackage pkg) { int num = pkg.ReadInt(); if (num < 0 || num > 10000) { throw new InvalidOperationException("Invalid sync entry count: " + num); } return num; } } }