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 AwayFromHome v1.0.0
plugins/AwayFromHome.dll
Decompiled 3 days 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.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using AwayFromHome.Compat; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; using Microsoft.CodeAnalysis; using ServerSync; using TMPro; using UnityEngine; using UnityEngine.Rendering; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: AssemblyCompany("AwayFromHome")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyDescription("Keeps ranches breeding, smelters smelting, and outposts running while you're elsewhere in the world.")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("AwayFromHome")] [assembly: AssemblyTitle("AwayFromHome")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ServerSync { [PublicAPI] public abstract class OwnConfigEntryBase { public object? LocalBaseValue; public bool SynchronizedConfig = true; public abstract ConfigEntryBase BaseConfig { get; } } [PublicAPI] public class SyncedConfigEntry<T>(ConfigEntry<T> sourceConfig) : OwnConfigEntryBase() { public readonly ConfigEntry<T> SourceConfig = sourceConfig; public override ConfigEntryBase BaseConfig => (ConfigEntryBase)(object)SourceConfig; public T Value { get { return SourceConfig.Value; } set { SourceConfig.Value = value; } } public void AssignLocalValue(T value) { if (LocalBaseValue == null) { Value = value; } else { LocalBaseValue = value; } } } public abstract class CustomSyncedValueBase { public object? LocalBaseValue; public readonly string Identifier; public readonly Type Type; private object? boxedValue; protected bool localIsOwner; public readonly int Priority; public object? BoxedValue { get { return boxedValue; } set { boxedValue = value; this.ValueChanged?.Invoke(); } } public event Action? ValueChanged; protected CustomSyncedValueBase(ConfigSync configSync, string identifier, Type type, int priority) { Priority = priority; Identifier = identifier; Type = type; configSync.AddCustomValue(this); localIsOwner = configSync.IsSourceOfTruth; configSync.SourceOfTruthChanged += delegate(bool truth) { localIsOwner = truth; }; } } [PublicAPI] public sealed class CustomSyncedValue<T> : CustomSyncedValueBase { public T Value { get { return (T)base.BoxedValue; } set { base.BoxedValue = value; } } public CustomSyncedValue(ConfigSync configSync, string identifier, T value = default(T), int priority = 0) : base(configSync, identifier, typeof(T), priority) { Value = value; } public void AssignLocalValue(T value) { if (localIsOwner) { Value = value; } else { LocalBaseValue = value; } } } internal class ConfigurationManagerAttributes { [UsedImplicitly] public bool? ReadOnly = false; } [PublicAPI] public class ConfigSync { [HarmonyPatch(typeof(ZRpc), "HandlePackage")] private static class SnatchCurrentlyHandlingRPC { public static ZRpc? currentRpc; [HarmonyPrefix] private static void Prefix(ZRpc __instance) { currentRpc = __instance; } } [HarmonyPatch(typeof(ZNet), "Awake")] internal static class RegisterRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance) { isServer = __instance.IsServer(); foreach (ConfigSync configSync2 in configSyncs) { ZRoutedRpc.instance.Register<ZPackage>(configSync2.Name + " ConfigSync", (Action<long, ZPackage>)configSync2.RPC_FromOtherClientConfigSync); if (isServer) { configSync2.InitialSyncDone = true; Debug.Log((object)("Registered '" + configSync2.Name + " ConfigSync' RPC - waiting for incoming connections")); } } if (isServer) { ((MonoBehaviour)__instance).StartCoroutine(WatchAdminListChanges()); } static void SendAdmin(List<ZNetPeer> peers, bool isAdmin) { ZPackage package = ConfigsToPackage(null, null, new PackageEntry[1] { new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = isAdmin } }); ConfigSync configSync = configSyncs.First(); if (configSync != null) { ((MonoBehaviour)ZNet.instance).StartCoroutine(configSync.sendZPackage(peers, package)); } } static IEnumerator WatchAdminListChanges() { MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); List<string> CurrentList = new List<string>(adminList.GetList()); while (true) { yield return (object)new WaitForSeconds(30f); if (!adminList.GetList().SequenceEqual(CurrentList)) { CurrentList = new List<string>(adminList.GetList()); List<ZNetPeer> adminPeer = ZNet.instance.GetPeers().Where(delegate(ZNetPeer p) { string hostName = p.m_rpc.GetSocket().GetHostName(); return ((object)listContainsId == null) ? adminList.Contains(hostName) : ((bool)listContainsId.Invoke(ZNet.instance, new object[2] { adminList, hostName })); }).ToList(); List<ZNetPeer> nonAdminPeer = ZNet.instance.GetPeers().Except(adminPeer).ToList(); SendAdmin(nonAdminPeer, isAdmin: false); SendAdmin(adminPeer, isAdmin: true); } } } } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] private static class RegisterClientRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance, ZNetPeer peer) { if (__instance.IsServer()) { return; } foreach (ConfigSync configSync in configSyncs) { peer.m_rpc.Register<ZPackage>(configSync.Name + " ConfigSync", (Action<ZRpc, ZPackage>)configSync.RPC_FromServerConfigSync); } } } private class ParsedConfigs { public readonly Dictionary<OwnConfigEntryBase, object?> configValues = new Dictionary<OwnConfigEntryBase, object>(); public readonly Dictionary<CustomSyncedValueBase, object?> customValues = new Dictionary<CustomSyncedValueBase, object>(); } [HarmonyPatch(typeof(ZNet), "Shutdown")] private class ResetConfigsOnShutdown { [HarmonyPostfix] private static void Postfix() { ProcessingServerUpdate = true; foreach (ConfigSync configSync in configSyncs) { configSync.resetConfigsFromServer(); configSync.IsSourceOfTruth = true; configSync.InitialSyncDone = false; } ProcessingServerUpdate = false; } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] private class SendConfigsAfterLogin { private 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; public BufferingSocket(ISocket original) { Original = original; ((ZPlayFabSocket)this)..ctor(); } public bool IsConnected() { return Original.IsConnected(); } public ZPackage Recv() { return Original.Recv(); } public int GetSendQueueSize() { return Original.GetSendQueueSize(); } public int GetCurrentSendRate() { return Original.GetCurrentSendRate(); } public bool IsHost() { return Original.IsHost(); } public void Dispose() { Original.Dispose(); } public bool GotNewData() { return Original.GotNewData(); } public void Close() { Original.Close(); } public string GetEndPointString() { return Original.GetEndPointString(); } public void GetAndResetStats(out int totalSent, out int totalRecv) { Original.GetAndResetStats(ref totalSent, ref totalRecv); } public void GetConnectionQuality(out float localQuality, out float remoteQuality, out int ping, out float outByteSec, out float inByteSec) { Original.GetConnectionQuality(ref localQuality, ref remoteQuality, ref ping, ref outByteSec, ref inByteSec); } public ISocket Accept() { return Original.Accept(); } public int GetHostPort() { return Original.GetHostPort(); } public bool Flush() { return Original.Flush(); } public string GetHostName() { return Original.GetHostName(); } public void VersionMatch() { if (finished) { Original.VersionMatch(); } else { versionMatchQueued = Package.Count; } } public void Send(ZPackage pkg) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown int pos = pkg.GetPos(); pkg.SetPos(0); int num = pkg.ReadInt(); if ((num == StringExtensionMethods.GetStableHashCode("PeerInfo") || num == StringExtensionMethods.GetStableHashCode("RoutedRPC") || num == StringExtensionMethods.GetStableHashCode("ZDOData")) && !finished) { ZPackage val = new ZPackage(pkg.GetArray()); val.SetPos(pos); Package.Add(val); } else { pkg.SetPos(pos); Original.Send(pkg); } } } [HarmonyPriority(800)] [HarmonyPrefix] private static void Prefix(ref Dictionary<Assembly, BufferingSocket>? __state, ZNet __instance, ZRpc rpc) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Invalid comparison between Unknown and I4 if (!__instance.IsServer()) { return; } BufferingSocket bufferingSocket = new BufferingSocket(rpc.GetSocket()); AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket); object? obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); ZNetPeer val = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (val != null && (int)ZNet.m_onlineBackend > 0) { FieldInfo fieldInfo = AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket"); object? value = fieldInfo.GetValue(val); ZPlayFabSocket val2 = (ZPlayFabSocket)((value is ZPlayFabSocket) ? value : null); if (val2 != null) { typeof(ZPlayFabSocket).GetField("m_remotePlayerId").SetValue(bufferingSocket, val2.m_remotePlayerId); } fieldInfo.SetValue(val, bufferingSocket); } if (__state == null) { __state = new Dictionary<Assembly, BufferingSocket>(); } __state[Assembly.GetExecutingAssembly()] = bufferingSocket; } [HarmonyPostfix] private static void Postfix(Dictionary<Assembly, BufferingSocket> __state, ZNet __instance, ZRpc rpc) { ZNetPeer peer; if (__instance.IsServer()) { object obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); peer = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (peer == null) { SendBufferedData(); } else { ((MonoBehaviour)__instance).StartCoroutine(sendAsync()); } } void SendBufferedData() { if (rpc.GetSocket() is BufferingSocket bufferingSocket) { AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket.Original); object? obj2 = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); ZNetPeer val = (ZNetPeer)((obj2 is ZNetPeer) ? obj2 : null); if (val != null) { AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket").SetValue(val, bufferingSocket.Original); } } BufferingSocket bufferingSocket2 = __state[Assembly.GetExecutingAssembly()]; bufferingSocket2.finished = true; for (int i = 0; i < bufferingSocket2.Package.Count; i++) { if (i == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } bufferingSocket2.Original.Send(bufferingSocket2.Package[i]); } if (bufferingSocket2.Package.Count == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } } IEnumerator sendAsync() { foreach (ConfigSync configSync in configSyncs) { List<PackageEntry> entries = new List<PackageEntry>(); if (configSync.CurrentVersion != null) { entries.Add(new PackageEntry { section = "Internal", key = "serverversion", type = typeof(string), value = configSync.CurrentVersion }); } MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); entries.Add(new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = (((object)listContainsId == null) ? ((object)adminList.Contains(rpc.GetSocket().GetHostName())) : listContainsId.Invoke(ZNet.instance, new object[2] { adminList, rpc.GetSocket().GetHostName() })) }); ZPackage package = ConfigsToPackage(configSync.allConfigs.Select((OwnConfigEntryBase c) => c.BaseConfig), configSync.allCustomValues, entries, partial: false); yield return ((MonoBehaviour)__instance).StartCoroutine(configSync.sendZPackage(new List<ZNetPeer> { peer }, package)); } SendBufferedData(); } } } private class PackageEntry { public string section = null; public string key = null; public Type type = null; public object? value; } [HarmonyPatch(typeof(ConfigEntryBase), "GetSerializedValue")] private static class PreventSavingServerInfo { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, ref string __result) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || isWritableConfig(ownConfigEntryBase)) { return true; } __result = TomlTypeConverter.ConvertToString(ownConfigEntryBase.LocalBaseValue, __instance.SettingType); return false; } } [HarmonyPatch(typeof(ConfigEntryBase), "SetSerializedValue")] private static class PreventConfigRereadChangingValues { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, string value) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || ownConfigEntryBase.LocalBaseValue == null) { return true; } try { ownConfigEntryBase.LocalBaseValue = TomlTypeConverter.ConvertToValue(value, __instance.SettingType); } catch (Exception ex) { Debug.LogWarning((object)$"Config value of setting \"{__instance.Definition}\" could not be parsed and will be ignored. Reason: {ex.Message}; Value: {value}"); } return false; } } private class InvalidDeserializationTypeException : Exception { public string expected = null; public string received = null; public string field = ""; } public static bool ProcessingServerUpdate; public readonly string Name; public string? DisplayName; public string? CurrentVersion; public string? MinimumRequiredVersion; public bool ModRequired = false; private bool? forceConfigLocking; private bool isSourceOfTruth = true; private static readonly HashSet<ConfigSync> configSyncs; private readonly HashSet<OwnConfigEntryBase> allConfigs = new HashSet<OwnConfigEntryBase>(); private HashSet<CustomSyncedValueBase> allCustomValues = new HashSet<CustomSyncedValueBase>(); private static bool isServer; private static bool lockExempt; private OwnConfigEntryBase? lockedConfig = null; private const byte PARTIAL_CONFIGS = 1; private const byte FRAGMENTED_CONFIG = 2; private const byte COMPRESSED_CONFIG = 4; private readonly Dictionary<string, SortedDictionary<int, byte[]>> configValueCache = new Dictionary<string, SortedDictionary<int, byte[]>>(); private readonly List<KeyValuePair<long, string>> cacheExpirations = new List<KeyValuePair<long, string>>(); private static long packageCounter; public bool IsLocked { get { bool? flag = forceConfigLocking; bool num; if (!flag.HasValue) { if (lockedConfig == null) { goto IL_0052; } num = ((IConvertible)lockedConfig.BaseConfig.BoxedValue).ToInt32(CultureInfo.InvariantCulture) != 0; } else { num = flag == true; } if (!num) { goto IL_0052; } int result = ((!lockExempt) ? 1 : 0); goto IL_0053; IL_0052: result = 0; goto IL_0053; IL_0053: return (byte)result != 0; } set { forceConfigLocking = value; } } public bool IsAdmin => lockExempt || isSourceOfTruth; public bool IsSourceOfTruth { get { return isSourceOfTruth; } private set { if (value != isSourceOfTruth) { isSourceOfTruth = value; this.SourceOfTruthChanged?.Invoke(value); } } } public bool InitialSyncDone { get; private set; } = false; public event Action<bool>? SourceOfTruthChanged; private event Action? lockedConfigChanged; static ConfigSync() { ProcessingServerUpdate = false; configSyncs = new HashSet<ConfigSync>(); lockExempt = false; packageCounter = 0L; RuntimeHelpers.RunClassConstructor(typeof(VersionCheck).TypeHandle); } public ConfigSync(string name) { Name = name; configSyncs.Add(this); new VersionCheck(this); } public SyncedConfigEntry<T> AddConfigEntry<T>(ConfigEntry<T> configEntry) { OwnConfigEntryBase ownConfigEntryBase = configData((ConfigEntryBase)(object)configEntry); SyncedConfigEntry<T> syncedEntry = ownConfigEntryBase as SyncedConfigEntry<T>; if (syncedEntry == null) { syncedEntry = new SyncedConfigEntry<T>(configEntry); 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 { if (!ProcessingServerUpdate && syncedEntry.SynchronizedConfig) { Broadcast(ZRoutedRpc.Everybody, (ConfigEntryBase)configEntry); } }; allConfigs.Add(syncedEntry); } return syncedEntry; } 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); lockingConfig.SettingChanged += delegate { this.lockedConfigChanged?.Invoke(); }; return (SyncedConfigEntry<T>)lockedConfig; } internal void AddCustomValue(CustomSyncedValueBase customValue) { if (allCustomValues.Select((CustomSyncedValueBase v) => v.Identifier).Concat(new string[1] { "serverversion" }).Contains(customValue.Identifier)) { throw new Exception("Cannot have multiple settings with the same name or with a reserved name (serverversion)"); } allCustomValues.Add(customValue); allCustomValues = new HashSet<CustomSyncedValueBase>(allCustomValues.OrderByDescending((CustomSyncedValueBase v) => v.Priority)); customValue.ValueChanged += delegate { if (!ProcessingServerUpdate) { Broadcast(ZRoutedRpc.Everybody, customValue); } }; } private void RPC_FromServerConfigSync(ZRpc rpc, ZPackage package) { lockedConfigChanged += serverLockedSettingChanged; IsSourceOfTruth = false; if (HandleConfigSyncRPC(0L, package, clientUpdate: false)) { InitialSyncDone = true; } } private void RPC_FromOtherClientConfigSync(long sender, ZPackage package) { HandleConfigSyncRPC(sender, package, clientUpdate: true); } private bool HandleConfigSyncRPC(long sender, ZPackage package, bool clientUpdate) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Expected O, but got Unknown //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Expected O, but got Unknown try { if (isServer && IsLocked) { ZRpc? currentRpc = SnatchCurrentlyHandlingRPC.currentRpc; object obj; if (currentRpc == null) { obj = null; } else { ISocket socket = currentRpc.GetSocket(); obj = ((socket != null) ? socket.GetHostName() : null); } string text = (string)obj; if (text != null) { MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList val = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); if (!(((object)methodInfo == null) ? val.Contains(text) : ((bool)methodInfo.Invoke(ZNet.instance, new object[2] { val, text })))) { return false; } } } cacheExpirations.RemoveAll(delegate(KeyValuePair<long, string> kv) { if (kv.Key < DateTimeOffset.Now.Ticks) { configValueCache.Remove(kv.Value); return true; } return false; }); byte b = package.ReadByte(); if ((b & 2) != 0) { long num = package.ReadLong(); string text2 = sender.ToString() + num; if (!configValueCache.TryGetValue(text2, out var value)) { value = new SortedDictionary<int, byte[]>(); configValueCache[text2] = value; cacheExpirations.Add(new KeyValuePair<long, string>(DateTimeOffset.Now.AddSeconds(60.0).Ticks, text2)); } int key = package.ReadInt(); int num2 = package.ReadInt(); value.Add(key, package.ReadByteArray()); if (value.Count < num2) { return false; } configValueCache.Remove(text2); package = new ZPackage(value.Values.SelectMany((byte[] a) => a).ToArray()); b = package.ReadByte(); } ProcessingServerUpdate = true; if ((b & 4) != 0) { byte[] buffer = package.ReadByteArray(); MemoryStream stream = new MemoryStream(buffer); MemoryStream memoryStream = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress)) { deflateStream.CopyTo(memoryStream); } package = new ZPackage(memoryStream.ToArray()); b = package.ReadByte(); } if ((b & 1) == 0) { resetConfigsFromServer(); } ParsedConfigs parsedConfigs = ReadConfigsFromPackage(package); ConfigFile val2 = null; bool saveOnConfigSet = false; foreach (KeyValuePair<OwnConfigEntryBase, object> configValue in parsedConfigs.configValues) { if (!isServer && configValue.Key.LocalBaseValue == null) { configValue.Key.LocalBaseValue = configValue.Key.BaseConfig.BoxedValue; } if (val2 == null) { val2 = configValue.Key.BaseConfig.ConfigFile; saveOnConfigSet = val2.SaveOnConfigSet; val2.SaveOnConfigSet = false; } configValue.Key.BaseConfig.BoxedValue = configValue.Value; } if (val2 != null) { val2.SaveOnConfigSet = saveOnConfigSet; val2.Save(); } foreach (KeyValuePair<CustomSyncedValueBase, object> customValue in parsedConfigs.customValues) { if (!isServer) { CustomSyncedValueBase key2 = customValue.Key; if (key2.LocalBaseValue == null) { key2.LocalBaseValue = customValue.Key.BoxedValue; } } customValue.Key.BoxedValue = customValue.Value; } Debug.Log((object)string.Format("Received {0} configs and {1} custom values from {2} for mod {3}", parsedConfigs.configValues.Count, parsedConfigs.customValues.Count, (isServer || clientUpdate) ? $"client {sender}" : "the server", DisplayName ?? Name)); if (!isServer) { serverLockedSettingChanged(); } return true; } finally { ProcessingServerUpdate = false; } } private ParsedConfigs ReadConfigsFromPackage(ZPackage package) { ParsedConfigs parsedConfigs = new ParsedConfigs(); Dictionary<string, OwnConfigEntryBase> dictionary = allConfigs.Where((OwnConfigEntryBase c) => c.SynchronizedConfig).ToDictionary((OwnConfigEntryBase c) => c.BaseConfig.Definition.Section + "_" + c.BaseConfig.Definition.Key, (OwnConfigEntryBase c) => c); Dictionary<string, CustomSyncedValueBase> dictionary2 = allCustomValues.ToDictionary((CustomSyncedValueBase c) => c.Identifier, (CustomSyncedValueBase c) => c); int num = package.ReadInt(); for (int num2 = 0; num2 < num; num2++) { string text = package.ReadString(); string text2 = package.ReadString(); string text3 = package.ReadString(); Type type = Type.GetType(text3); if (text3 == "" || type != null) { object obj; try { obj = ((text3 == "") ? null : ReadValueWithTypeFromZPackage(package, type)); } catch (InvalidDeserializationTypeException ex) { Debug.LogWarning((object)("Got unexpected struct internal type " + ex.received + " for field " + ex.field + " struct " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + ex.expected)); continue; } OwnConfigEntryBase value2; if (text == "Internal") { CustomSyncedValueBase value; if (text2 == "serverversion") { if (obj?.ToString() != CurrentVersion) { Debug.LogWarning((object)("Received server version is not equal: server version = " + (obj?.ToString() ?? "null") + "; local version = " + (CurrentVersion ?? "unknown"))); } } else if (text2 == "lockexempt") { if (obj is bool flag) { lockExempt = flag; } } else if (dictionary2.TryGetValue(text2, out value)) { if ((text3 == "" && (!value.Type.IsValueType || Nullable.GetUnderlyingType(value.Type) != null)) || GetZPackageTypeString(value.Type) == text3) { parsedConfigs.customValues[value] = obj; continue; } Debug.LogWarning((object)("Got unexpected type " + text3 + " for internal value " + text2 + " for mod " + (DisplayName ?? Name) + ", expecting " + value.Type.AssemblyQualifiedName)); } } else if (dictionary.TryGetValue(text + "_" + text2, out value2)) { Type type2 = configType(value2.BaseConfig); if ((text3 == "" && (!type2.IsValueType || Nullable.GetUnderlyingType(type2) != null)) || GetZPackageTypeString(type2) == text3) { parsedConfigs.configValues[value2] = obj; continue; } Debug.LogWarning((object)("Got unexpected type " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + type2.AssemblyQualifiedName)); } else { Debug.LogWarning((object)("Received unknown config entry " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ". This may happen if client and server versions of the mod do not match.")); } continue; } Debug.LogWarning((object)("Got invalid type " + text3 + ", abort reading of received configs")); return new ParsedConfigs(); } return parsedConfigs; } private static bool isWritableConfig(OwnConfigEntryBase config) { ConfigSync configSync = configSyncs.FirstOrDefault((ConfigSync cs) => cs.allConfigs.Contains(config)); if (configSync == null) { return true; } return configSync.IsSourceOfTruth || !config.SynchronizedConfig || config.LocalBaseValue == null || (!configSync.IsLocked && (config != configSync.lockedConfig || lockExempt)); } private void serverLockedSettingChanged() { foreach (OwnConfigEntryBase allConfig in allConfigs) { configAttribute<ConfigurationManagerAttributes>(allConfig.BaseConfig).ReadOnly = !isWritableConfig(allConfig); } } private void resetConfigsFromServer() { ConfigFile val = null; bool saveOnConfigSet = false; foreach (OwnConfigEntryBase item in allConfigs.Where((OwnConfigEntryBase config) => config.LocalBaseValue != null)) { if (val == null) { val = item.BaseConfig.ConfigFile; saveOnConfigSet = val.SaveOnConfigSet; val.SaveOnConfigSet = false; } item.BaseConfig.BoxedValue = item.LocalBaseValue; item.LocalBaseValue = null; } if (val != null) { val.SaveOnConfigSet = saveOnConfigSet; } foreach (CustomSyncedValueBase item2 in allCustomValues.Where((CustomSyncedValueBase config) => config.LocalBaseValue != null)) { item2.BoxedValue = item2.LocalBaseValue; item2.LocalBaseValue = null; } lockedConfigChanged -= serverLockedSettingChanged; serverLockedSettingChanged(); } private IEnumerator<bool> distributeConfigToPeers(ZNetPeer peer, ZPackage package) { ZRoutedRpc rpc = ZRoutedRpc.instance; if (rpc == null) { yield break; } byte[] data = package.GetArray(); if (data != null && data.LongLength > 250000) { int fragments = (int)(1 + (data.LongLength - 1) / 250000); long packageIdentifier = ++packageCounter; int fragment = 0; while (fragment < fragments) { foreach (bool item in waitForQueue()) { yield return item; } if (peer.m_socket.IsConnected()) { ZPackage fragmentedPackage = new ZPackage(); fragmentedPackage.Write((byte)2); fragmentedPackage.Write(packageIdentifier); fragmentedPackage.Write(fragment); fragmentedPackage.Write(fragments); fragmentedPackage.Write(data.Skip(250000 * fragment).Take(250000).ToArray()); SendPackage(fragmentedPackage); if (fragment != fragments - 1) { yield return true; } int num = fragment + 1; fragment = num; continue; } break; } yield break; } foreach (bool item2 in waitForQueue()) { yield return item2; } SendPackage(package); void SendPackage(ZPackage pkg) { string text = Name + " ConfigSync"; if (isServer) { peer.m_rpc.Invoke(text, new object[1] { pkg }); } else { rpc.InvokeRoutedRPC(peer.m_server ? 0 : peer.m_uid, text, new object[1] { pkg }); } } IEnumerable<bool> waitForQueue() { float timeout = Time.time + 30f; while (peer.m_socket.GetSendQueueSize() > 20000) { if (Time.time > timeout) { Debug.Log((object)$"Disconnecting {peer.m_uid} after 30 seconds config sending timeout"); peer.m_rpc.Invoke("Error", new object[1] { (object)(ConnectionStatus)5 }); ZNet.instance.Disconnect(peer); break; } yield return false; } } } private IEnumerator sendZPackage(long target, ZPackage package) { if (!Object.op_Implicit((Object)(object)ZNet.instance)) { return Enumerable.Empty<object>().GetEnumerator(); } List<ZNetPeer> list = (List<ZNetPeer>)AccessTools.DeclaredField(typeof(ZRoutedRpc), "m_peers").GetValue(ZRoutedRpc.instance); if (target != ZRoutedRpc.Everybody) { list = list.Where((ZNetPeer p) => p.m_uid == target).ToList(); } return sendZPackage(list, package); } private IEnumerator sendZPackage(List<ZNetPeer> peers, ZPackage package) { if (!Object.op_Implicit((Object)(object)ZNet.instance)) { yield break; } byte[] rawData = package.GetArray(); if (rawData != null && rawData.LongLength > 10000) { ZPackage compressedPackage = new ZPackage(); compressedPackage.Write((byte)4); MemoryStream output = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(output, CompressionLevel.Optimal)) { deflateStream.Write(rawData, 0, rawData.Length); } compressedPackage.Write(output.ToArray()); package = compressedPackage; } List<IEnumerator<bool>> writers = (from p in peers where p.IsReady() select distributeConfigToPeers(p, package)).ToList(); writers.RemoveAll((IEnumerator<bool> writer) => !writer.MoveNext()); while (writers.Count > 0) { yield return null; writers.RemoveAll((IEnumerator<bool> writer) => !writer.MoveNext()); } } private void Broadcast(long target, params ConfigEntryBase[] configs) { if (!IsLocked || isServer) { ZPackage package = ConfigsToPackage(configs); ZNet instance = ZNet.instance; if (instance != null) { ((MonoBehaviour)instance).StartCoroutine(sendZPackage(target, package)); } } } private void Broadcast(long target, params CustomSyncedValueBase[] customValues) { if (!IsLocked || isServer) { ZPackage package = ConfigsToPackage(null, customValues); ZNet instance = ZNet.instance; if (instance != null) { ((MonoBehaviour)instance).StartCoroutine(sendZPackage(target, package)); } } } private static OwnConfigEntryBase? configData(ConfigEntryBase config) { return config.Description.Tags?.OfType<OwnConfigEntryBase>().SingleOrDefault(); } public static SyncedConfigEntry<T>? ConfigData<T>(ConfigEntry<T> config) { return ((ConfigEntryBase)config).Description.Tags?.OfType<SyncedConfigEntry<T>>().SingleOrDefault(); } private static T configAttribute<T>(ConfigEntryBase config) { return config.Description.Tags.OfType<T>().First(); } private static Type configType(ConfigEntryBase config) { return configType(config.SettingType); } private static Type configType(Type type) { return type.IsEnum ? Enum.GetUnderlyingType(type) : type; } private static ZPackage ConfigsToPackage(IEnumerable<ConfigEntryBase>? configs = null, IEnumerable<CustomSyncedValueBase>? customValues = null, IEnumerable<PackageEntry>? packageEntries = null, bool partial = true) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown List<ConfigEntryBase> list = configs?.Where((ConfigEntryBase config) => configData(config).SynchronizedConfig).ToList() ?? new List<ConfigEntryBase>(); List<CustomSyncedValueBase> list2 = customValues?.ToList() ?? new List<CustomSyncedValueBase>(); ZPackage val = new ZPackage(); val.Write((byte)(partial ? 1 : 0)); val.Write(list.Count + list2.Count + (packageEntries?.Count() ?? 0)); foreach (PackageEntry item in packageEntries ?? Array.Empty<PackageEntry>()) { AddEntryToPackage(val, item); } foreach (CustomSyncedValueBase item2 in list2) { AddEntryToPackage(val, new PackageEntry { section = "Internal", key = item2.Identifier, type = item2.Type, value = item2.BoxedValue }); } foreach (ConfigEntryBase item3 in list) { AddEntryToPackage(val, new PackageEntry { section = item3.Definition.Section, key = item3.Definition.Key, type = configType(item3), value = item3.BoxedValue }); } return val; } private static void AddEntryToPackage(ZPackage package, PackageEntry entry) { package.Write(entry.section); package.Write(entry.key); package.Write((entry.value == null) ? "" : GetZPackageTypeString(entry.type)); AddValueToZPackage(package, entry.value); } private static string GetZPackageTypeString(Type type) { return type.AssemblyQualifiedName; } private static void AddValueToZPackage(ZPackage package, object? value) { Type type = value?.GetType(); if (value is Enum) { value = ((IConvertible)value).ToType(Enum.GetUnderlyingType(value.GetType()), CultureInfo.InvariantCulture); } else { if (value is ICollection collection) { package.Write(collection.Count); { foreach (object item in collection) { AddValueToZPackage(package, item); } return; } } if ((object)type != null && type.IsValueType && !type.IsPrimitive) { FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); package.Write(fields.Length); FieldInfo[] array = fields; foreach (FieldInfo fieldInfo in array) { package.Write(GetZPackageTypeString(fieldInfo.FieldType)); AddValueToZPackage(package, fieldInfo.GetValue(value)); } return; } } ZRpc.Serialize(new object[1] { value }, ref package); } private static object ReadValueWithTypeFromZPackage(ZPackage package, Type type) { if ((object)type != null && type.IsValueType && !type.IsPrimitive && !type.IsEnum) { FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); int num = package.ReadInt(); if (num != fields.Length) { throw new InvalidDeserializationTypeException { received = $"(field count: {num})", expected = $"(field count: {fields.Length})" }; } object uninitializedObject = FormatterServices.GetUninitializedObject(type); FieldInfo[] array = fields; foreach (FieldInfo fieldInfo in array) { string text = package.ReadString(); if (text != GetZPackageTypeString(fieldInfo.FieldType)) { throw new InvalidDeserializationTypeException { received = text, expected = GetZPackageTypeString(fieldInfo.FieldType), field = fieldInfo.Name }; } fieldInfo.SetValue(uninitializedObject, ReadValueWithTypeFromZPackage(package, fieldInfo.FieldType)); } return uninitializedObject; } if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<, >)) { int num2 = package.ReadInt(); IDictionary dictionary = (IDictionary)Activator.CreateInstance(type); Type type2 = typeof(KeyValuePair<, >).MakeGenericType(type.GenericTypeArguments); FieldInfo field = type2.GetField("key", BindingFlags.Instance | BindingFlags.NonPublic); FieldInfo field2 = type2.GetField("value", BindingFlags.Instance | BindingFlags.NonPublic); for (int j = 0; j < num2; j++) { object obj = ReadValueWithTypeFromZPackage(package, type2); dictionary.Add(field.GetValue(obj), field2.GetValue(obj)); } return dictionary; } if (type != typeof(List<string>) && type.IsGenericType) { Type type3 = typeof(ICollection<>).MakeGenericType(type.GenericTypeArguments[0]); if ((object)type3 != null && type3.IsAssignableFrom(type)) { int num3 = package.ReadInt(); object obj2 = Activator.CreateInstance(type); MethodInfo method = type3.GetMethod("Add"); for (int k = 0; k < num3; k++) { method.Invoke(obj2, new object[1] { ReadValueWithTypeFromZPackage(package, type.GenericTypeArguments[0]) }); } return obj2; } } ParameterInfo parameterInfo = (ParameterInfo)FormatterServices.GetUninitializedObject(typeof(ParameterInfo)); AccessTools.DeclaredField(typeof(ParameterInfo), "ClassImpl").SetValue(parameterInfo, type); List<object> source = new List<object>(); ZRpc.Deserialize(new ParameterInfo[2] { null, parameterInfo }, package, ref source); return source.First(); } } [PublicAPI] [HarmonyPatch] public class VersionCheck { private static readonly HashSet<VersionCheck> versionChecks; private static readonly Dictionary<string, string> notProcessedNames; public string Name; private string? displayName; private string? currentVersion; private string? minimumRequiredVersion; public bool ModRequired = true; private string? ReceivedCurrentVersion; private string? ReceivedMinimumRequiredVersion; private readonly List<ZRpc> ValidatedClients = new List<ZRpc>(); private ConfigSync? ConfigSync; public string DisplayName { get { return displayName ?? Name; } set { displayName = value; } } public string CurrentVersion { get { return currentVersion ?? "0.0.0"; } set { currentVersion = value; } } public string MinimumRequiredVersion { get { return minimumRequiredVersion ?? (ModRequired ? CurrentVersion : "0.0.0"); } set { minimumRequiredVersion = value; } } private static void PatchServerSync() { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown Patches patchInfo = PatchProcessor.GetPatchInfo((MethodBase)AccessTools.DeclaredMethod(typeof(ZNet), "Awake", (Type[])null, (Type[])null)); if (patchInfo != null && patchInfo.Postfixes.Count((Patch p) => p.PatchMethod.DeclaringType == typeof(ConfigSync.RegisterRPCPatch)) > 0) { return; } Harmony val = new Harmony("org.bepinex.helpers.ServerSync"); foreach (Type item in from t in typeof(ConfigSync).GetNestedTypes(BindingFlags.NonPublic).Concat(new Type[1] { typeof(VersionCheck) }) where t.IsClass select t) { val.PatchAll(item); } } static VersionCheck() { versionChecks = new HashSet<VersionCheck>(); notProcessedNames = new Dictionary<string, string>(); typeof(ThreadingHelper).GetMethod("StartSyncInvoke").Invoke(ThreadingHelper.Instance, new object[1] { new Action(PatchServerSync) }); } public VersionCheck(string name) { Name = name; ModRequired = true; versionChecks.Add(this); } public VersionCheck(ConfigSync configSync) { ConfigSync = configSync; Name = ConfigSync.Name; versionChecks.Add(this); } public void Initialize() { ReceivedCurrentVersion = null; ReceivedMinimumRequiredVersion = null; if (ConfigSync != null) { Name = ConfigSync.Name; DisplayName = ConfigSync.DisplayName; CurrentVersion = ConfigSync.CurrentVersion; MinimumRequiredVersion = ConfigSync.MinimumRequiredVersion; ModRequired = ConfigSync.ModRequired; } } private bool IsVersionOk() { if (ReceivedMinimumRequiredVersion == null || ReceivedCurrentVersion == null) { return !ModRequired; } bool flag = new Version(CurrentVersion) >= new Version(ReceivedMinimumRequiredVersion); bool flag2 = new Version(ReceivedCurrentVersion) >= new Version(MinimumRequiredVersion); return flag && flag2; } private string ErrorClient() { if (ReceivedMinimumRequiredVersion == null) { return DisplayName + " is not installed on the server."; } return (new Version(CurrentVersion) >= new Version(ReceivedMinimumRequiredVersion)) ? (DisplayName + " may not be higher than version " + ReceivedCurrentVersion + ". You have version " + CurrentVersion + ".") : (DisplayName + " needs to be at least version " + ReceivedMinimumRequiredVersion + ". You have version " + CurrentVersion + "."); } private string ErrorServer(ZRpc rpc) { return "Disconnect: The client (" + rpc.GetSocket().GetHostName() + ") doesn't have the correct " + DisplayName + " version " + MinimumRequiredVersion; } private string Error(ZRpc? rpc = null) { return (rpc == null) ? ErrorClient() : ErrorServer(rpc); } private static VersionCheck[] GetFailedClient() { return versionChecks.Where((VersionCheck check) => !check.IsVersionOk()).ToArray(); } private static VersionCheck[] GetFailedServer(ZRpc rpc) { return versionChecks.Where((VersionCheck check) => check.ModRequired && !check.ValidatedClients.Contains(rpc)).ToArray(); } private static void Logout() { Game.instance.Logout(true, true); AccessTools.DeclaredField(typeof(ZNet), "m_connectionStatus").SetValue(null, (object)(ConnectionStatus)3); } private static void DisconnectClient(ZRpc rpc) { rpc.Invoke("Error", new object[1] { 3 }); } private static void CheckVersion(ZRpc rpc, ZPackage pkg) { CheckVersion(rpc, pkg, null); } private static void CheckVersion(ZRpc rpc, ZPackage pkg, Action<ZRpc, ZPackage>? original) { string text = pkg.ReadString(); string text2 = pkg.ReadString(); string text3 = pkg.ReadString(); bool flag = false; foreach (VersionCheck versionCheck in versionChecks) { if (!(text != versionCheck.Name)) { Debug.Log((object)("Received " + versionCheck.DisplayName + " version " + text3 + " and minimum version " + text2 + " from the " + (ZNet.instance.IsServer() ? "client" : "server") + ".")); versionCheck.ReceivedMinimumRequiredVersion = text2; versionCheck.ReceivedCurrentVersion = text3; if (ZNet.instance.IsServer() && versionCheck.IsVersionOk()) { versionCheck.ValidatedClients.Add(rpc); } flag = true; } } if (flag) { return; } pkg.SetPos(0); if (original != null) { original(rpc, pkg); if (pkg.GetPos() == 0) { notProcessedNames.Add(text, text3); } } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] [HarmonyPrefix] private static bool RPC_PeerInfo(ZRpc rpc, ZNet __instance) { VersionCheck[] array = (__instance.IsServer() ? GetFailedServer(rpc) : GetFailedClient()); if (array.Length == 0) { return true; } VersionCheck[] array2 = array; foreach (VersionCheck versionCheck in array2) { Debug.LogWarning((object)versionCheck.Error(rpc)); } if (__instance.IsServer()) { DisconnectClient(rpc); } else { Logout(); } return false; } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] [HarmonyPrefix] private static void RegisterAndCheckVersion(ZNetPeer peer, ZNet __instance) { //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Expected O, but got Unknown notProcessedNames.Clear(); IDictionary dictionary = (IDictionary)typeof(ZRpc).GetField("m_functions", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(peer.m_rpc); if (dictionary.Contains(StringExtensionMethods.GetStableHashCode("ServerSync VersionCheck"))) { object obj = dictionary[StringExtensionMethods.GetStableHashCode("ServerSync VersionCheck")]; Action<ZRpc, ZPackage> action = (Action<ZRpc, ZPackage>)obj.GetType().GetField("m_action", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(obj); peer.m_rpc.Register<ZPackage>("ServerSync VersionCheck", (Action<ZRpc, ZPackage>)delegate(ZRpc rpc, ZPackage pkg) { CheckVersion(rpc, pkg, action); }); } else { peer.m_rpc.Register<ZPackage>("ServerSync VersionCheck", (Action<ZRpc, ZPackage>)CheckVersion); } foreach (VersionCheck versionCheck in versionChecks) { versionCheck.Initialize(); if (versionCheck.ModRequired || __instance.IsServer()) { Debug.Log((object)("Sending " + versionCheck.DisplayName + " version " + versionCheck.CurrentVersion + " and minimum version " + versionCheck.MinimumRequiredVersion + " to the " + (__instance.IsServer() ? "client" : "server") + ".")); ZPackage val = new ZPackage(); val.Write(versionCheck.Name); val.Write(versionCheck.MinimumRequiredVersion); val.Write(versionCheck.CurrentVersion); peer.m_rpc.Invoke("ServerSync VersionCheck", new object[1] { val }); } } } [HarmonyPatch(typeof(ZNet), "Disconnect")] [HarmonyPrefix] private static void RemoveDisconnected(ZNetPeer peer, ZNet __instance) { if (!__instance.IsServer()) { return; } foreach (VersionCheck versionCheck in versionChecks) { versionCheck.ValidatedClients.Remove(peer.m_rpc); } } [HarmonyPatch(typeof(FejdStartup), "ShowConnectError")] [HarmonyPostfix] private static void ShowConnectionError(FejdStartup __instance) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Invalid comparison between Unknown and I4 //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_021e: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) if (!__instance.m_connectionFailedPanel.activeSelf || (int)ZNet.GetConnectionStatus() != 3) { return; } bool flag = false; VersionCheck[] failedClient = GetFailedClient(); if (failedClient.Length != 0) { string text = string.Join("\n", failedClient.Select((VersionCheck check) => check.Error())); TMP_Text connectionFailedError = __instance.m_connectionFailedError; connectionFailedError.text = connectionFailedError.text + "\n" + text; flag = true; } foreach (KeyValuePair<string, string> item in notProcessedNames.OrderBy((KeyValuePair<string, string> kv) => kv.Key)) { if (!__instance.m_connectionFailedError.text.Contains(item.Key)) { TMP_Text connectionFailedError2 = __instance.m_connectionFailedError; connectionFailedError2.text = connectionFailedError2.text + "\nServer expects you to have " + item.Key + " (Version: " + item.Value + ") installed."; flag = true; } } if (flag) { RectTransform component = ((Component)__instance.m_connectionFailedPanel.transform.Find("Image")).GetComponent<RectTransform>(); Vector2 sizeDelta = component.sizeDelta; sizeDelta.x = 675f; component.sizeDelta = sizeDelta; __instance.m_connectionFailedError.ForceMeshUpdate(false, false); float num = __instance.m_connectionFailedError.renderedHeight + 105f; RectTransform component2 = ((Component)((Component)component).transform.Find("ButtonOk")).GetComponent<RectTransform>(); component2.anchoredPosition = new Vector2(component2.anchoredPosition.x, component2.anchoredPosition.y - (num - component.sizeDelta.y) / 2f); sizeDelta = component.sizeDelta; sizeDelta.y = num; component.sizeDelta = sizeDelta; } } } } namespace AwayFromHome { public static class AdminSites { public const string AdminKey = "afh_admin"; private static readonly Dictionary<long, bool> AdminByPlayerId = new Dictionary<long, bool>(); private static readonly List<string> PeerTrace = new List<string>(); public static void Refresh(IReadOnlyList<SiteRecord> sites) { //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_02fa: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Unknown result type (might be due to invalid IL or missing references) if (sites == null || sites.Count == 0 || !Configuration.adminBypass.Value) { return; } try { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || ZDOMan.instance == null) { return; } if ((Object)(object)Player.m_localPlayer != (Object)null) { long playerID = Player.m_localPlayer.GetPlayerID(); if (playerID != 0) { AdminByPlayerId[playerID] = true; } } PeerTrace.Clear(); List<ZNetPeer> peers = ZNet.instance.GetPeers(); if (peers != null) { foreach (ZNetPeer item in peers) { if (item == null) { PeerTrace.Add("<null peer>"); continue; } string text = string.Format("uid={0} host='{1}' name='{2}'", item.m_uid, (item.m_socket != null) ? item.m_socket.GetHostName() : "<no socket>", item.m_playerName); if (((ZDOID)(ref item.m_characterID)).IsNone()) { PeerTrace.Add(text + " -> NO characterID (character select?)"); continue; } ZDO zDO = ZDOMan.instance.GetZDO(item.m_characterID); if (zDO == null) { PeerTrace.Add($"{text} char={item.m_characterID} -> ZDO NOT FOUND"); continue; } if (!zDO.IsValid()) { PeerTrace.Add($"{text} char={item.m_characterID} -> ZDO INVALID"); continue; } long num = zDO.GetLong(ZDOVars.s_playerID, 0L); if (num == 0) { PeerTrace.Add($"{text} char={item.m_characterID} -> playerID field ABSENT/0"); continue; } bool flag = SiteRegistry.IsAuthorizedAdmin(item.m_uid); AdminByPlayerId[num] = flag; PeerTrace.Add($"{text} playerID={num} -> admin={flag}"); } } int num2 = 0; int num3 = 0; int num4 = 0; foreach (SiteRecord site in sites) { if (site != null && site.OwnerId != 0 && AdminByPlayerId.TryGetValue(site.OwnerId, out var value)) { num4++; if (value) { num3++; } if (Stamp(site.Id, value)) { num2++; } } } Diagnose(sites, num4, num3, num2); } catch (Exception arg) { Plugin.Log.LogError((object)$"AwayFromHome: refreshing which sites belong to admins failed (non-fatal, the previous answer stands). Reason: {arg}"); } } public static bool IsAdminOwned(ZDOID siteId, long ownerId) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) if (!Configuration.adminBypass.Value) { return false; } if (ownerId != 0L && AdminByPlayerId.TryGetValue(ownerId, out var value)) { return value; } return IsAdminOwned(siteId); } public static bool IsAdminOwned(ZDOID siteId) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) if (!Configuration.adminBypass.Value) { return false; } try { ZDOMan instance = ZDOMan.instance; ZDO val = ((instance != null) ? instance.GetZDO(siteId) : null); return val != null && val.IsValid() && val.GetBool("afh_admin", false); } catch (Exception arg) { Plugin.Log.LogError((object)$"AwayFromHome: reading a site's admin flag failed (non-fatal, treated as not exempt). Reason: {arg}"); return false; } } private static bool Stamp(ZDOID siteId, bool isAdmin) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) ZDOMan instance = ZDOMan.instance; ZDO val = ((instance != null) ? instance.GetZDO(siteId) : null); if (val == null || !val.IsValid()) { return false; } bool flag = val.GetBool("afh_admin", false); if (flag == isAdmin) { return isAdmin; } if (val.GetOwner() != ZDOMan.GetSessionID()) { return flag; } val.Set("afh_admin", isAdmin); bool flag2 = val.GetBool("afh_admin", false); if (flag2 != isAdmin) { Plugin.Log.LogWarning((object)$"AwayFromHome: wrote admin={isAdmin} to site {siteId} and read back {flag2} - the ZDO refused the write."); return false; } if (Configuration.verboseLogging.Value) { Plugin.Log.LogInfo((object)string.Format("AwayFromHome: site {0} is {1} flagged as admin-owned, so it is {2} MaxSitesPerPlayer.", siteId, isAdmin ? "now" : "no longer", isAdmin ? "exempt from" : "subject to")); } return isAdmin; } private static void Diagnose(IReadOnlyList<SiteRecord> sites, int matched, int exempt, int wrote) { if (!Configuration.verboseLogging.Value) { return; } try { Dictionary<long, int> dictionary = new Dictionary<long, int>(); foreach (SiteRecord site in sites) { if (site != null) { dictionary.TryGetValue(site.OwnerId, out var value); dictionary[site.OwnerId] = value + 1; } } List<string> list = new List<string>(); foreach (KeyValuePair<long, int> item in dictionary) { bool value2; bool flag = AdminByPlayerId.TryGetValue(item.Key, out value2); list.Add(string.Format("{0} x{1} ({2})", item.Key, item.Value, flag ? (value2 ? "connected admin" : "connected, NOT admin") : "no connected peer")); } Plugin.Log.LogInfo((object)(string.Format("AwayFromHome [admin-bypass diag]: {0} site(s); peers=[{1}]; ", sites.Count, string.Join(" | ", PeerTrace)) + string.Format("owners=[{0}]; matched={1}, exempt={2}, flag-written-this-pass={3}.", string.Join(" | ", list), matched, exempt, wrote))); } catch (Exception arg) { Plugin.Log.LogError((object)$"AwayFromHome: admin-bypass diagnostics failed (non-fatal). Reason: {arg}"); } } } [HarmonyPatch] public static class Commands { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__0_0; public static ConsoleEvent <>9__0_1; public static ConsoleEvent <>9__0_2; public static ConsoleEvent <>9__0_3; public static ConsoleEvent <>9__0_4; public static Func<PieceCategory, string> <>9__4_0; internal void <OnZNetStart>b__0_0(ConsoleEventArgs args) { RunList(args); } internal void <OnZNetStart>b__0_1(ConsoleEventArgs args) { RunStatus(args); } internal void <OnZNetStart>b__0_2(ConsoleEventArgs args) { RunAdminList(args); } internal void <OnZNetStart>b__0_3(ConsoleEventArgs args) { RunAdminRemove(args); } internal void <OnZNetStart>b__0_4(ConsoleEventArgs args) { RunDiag(args); } internal unsafe string <RunDiag>b__4_0(PieceCategory c) { return ((object)(*(PieceCategory*)(&c))/*cast due to .constrained prefix*/).ToString(); } } public static void OnZNetStart() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Expected O, but got Unknown //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Expected O, but got Unknown try { object obj = <>c.<>9__0_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { RunList(args); }; <>c.<>9__0_0 = val; obj = (object)val; } new ConsoleCommand("afh_list", "lists the Keeper Stones you have placed.", (ConsoleEvent)obj, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj2 = <>c.<>9__0_1; if (obj2 == null) { ConsoleEvent val2 = delegate(ConsoleEventArgs args) { RunStatus(args); }; <>c.<>9__0_1 = val2; obj2 = (object)val2; } new ConsoleCommand("afh_status", "shows what the keeper is doing right now.", (ConsoleEvent)obj2, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj3 = <>c.<>9__0_2; if (obj3 == null) { ConsoleEvent val3 = delegate(ConsoleEventArgs args) { RunAdminList(args); }; <>c.<>9__0_2 = val3; obj3 = (object)val3; } new ConsoleCommand("afh_admin_list", "admin only - lists every Keeper Stone placed by every player on this server.", (ConsoleEvent)obj3, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj4 = <>c.<>9__0_3; if (obj4 == null) { ConsoleEvent val4 = delegate(ConsoleEventArgs args) { RunAdminRemove(args); }; <>c.<>9__0_3 = val4; obj4 = (object)val4; } new ConsoleCommand("afh_admin_remove", "<index> - admin only. Removes ANY player's Keeper Stone by its number in afh_admin_list, including one you cannot reach.", (ConsoleEvent)obj4, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj5 = <>c.<>9__0_4; if (obj5 == null) { ConsoleEvent val5 = delegate(ConsoleEventArgs args) { RunDiag(args); }; <>c.<>9__0_4 = val5; obj5 = (object)val5; } new ConsoleCommand("afh_diag", "dumps why the Keeper Stone is or is not showing in the hammer's build menu.", (ConsoleEvent)obj5, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); Plugin.Log.LogInfo((object)"AwayFromHome: console commands afh_list / afh_status / afh_diag / afh_admin_list / afh_admin_remove registered."); } catch (Exception arg) { Plugin.Log.LogError((object)$"AwayFromHome: could not register console commands. Reason: {arg}"); } } private static void Say(ConsoleEventArgs args, string msg) { try { Terminal context = args.Context; if (context != null) { context.AddString(msg); } } catch { } Plugin.Log.LogInfo((object)("AwayFromHome: " + msg)); } private static void RunList(ConsoleEventArgs args) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) IReadOnlyList<SiteRecord> mySites = SiteRegistry.GetMySites(); if (mySites.Count == 0) { Say(args, "You have no Keeper Stones standing. Build one with the hammer at the ranch or workshop you want kept running."); return; } Say(args, $"{mySites.Count} Keeper Stone(s):"); foreach (SiteRecord item in mySites) { string text = ((KeeperManager.ActiveSiteId == item.Id) ? (" <- currently " + KeeperManager.ActivePhase) : ""); Say(args, " " + item.Label + text); } } private static void RunStatus(ConsoleEventArgs args) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) if (!Configuration.enabled.Value) { Say(args, "Away From Home is disabled (server setting)."); return; } Say(args, (KeeperManager.ActiveSiteId == ZDOID.None) ? "Idle - no site currently held (nothing standing, or between rotation passes)." : ("Holding " + KeeperManager.ActiveSiteLabel + " (owner: " + KeeperManager.ActiveSiteOwner + "): " + KeeperManager.ActivePhase + ".")); if (!string.IsNullOrEmpty(SiteRegistry.LastReply)) { Say(args, "Last server reply: " + (SiteRegistry.LastReplyOk ? "OK" : "FAILED") + " - " + SiteRegistry.LastReply); } } private unsafe static void RunDiag(ConsoleEventArgs args) { //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Expected I4, but got Unknown //IL_025f: Unknown result type (might be due to invalid IL or missing references) //IL_026f: Unknown result type (might be due to invalid IL or missing references) //IL_027f: Unknown result type (might be due to invalid IL or missing references) //IL_02a0: Unknown result type (might be due to invalid IL or missing references) //IL_0337: Unknown result type (might be due to invalid IL or missing references) //IL_0342: Unknown result type (might be due to invalid IL or missing references) //IL_0352: Unknown result type (might be due to invalid IL or missing references) //IL_02ee: Unknown result type (might be due to invalid IL or missing references) //IL_02f3: Unknown result type (might be due to invalid IL or missing references) //IL_02f7: Unknown result type (might be due to invalid IL or missing references) try { Say(args, "--- AwayFromHome diagnostics ---"); GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab("AFH_KeeperStone") : null); Say(args, string.Format("ZNetScene knows '{0}': {1} (hash {2})", "AFH_KeeperStone", ((Object)(object)val != (Object)null) ? "YES" : "NO", KeeperStone.PrefabHash)); if ((Object)(object)val == (Object)null) { Say(args, "-> registration never happened. Check the BepInEx log for an AwayFromHome error at startup."); return; } Piece p = val.GetComponent<Piece>(); if ((Object)(object)p == (Object)null) { Say(args, "-> the prefab has NO Piece component. That alone would hide it."); return; } Say(args, string.Format("Piece: name='{0}' enabled={1} category={2} ({3}) icon={4} station={5}", p.m_name, p.m_enabled, p.m_category, (int)p.m_category, ((Object)(object)p.m_icon != (Object)null) ? "set" : "NULL", ((Object)(object)p.m_craftingStation != (Object)null) ? ((Object)p.m_craftingStation).name : "none")); Say(args, $"Requirements: {((p.m_resources != null) ? p.m_resources.Length : 0)}"); if (p.m_resources != null) { Requirement[] resources = p.m_resources; foreach (Requirement val2 in resources) { string arg = (((Object)(object)val2?.m_resItem != (Object)null) ? ((Object)val2.m_resItem).name : "NULL"); Say(args, $" {arg} x{val2?.m_amount}"); } } MeshFilter componentInChildren = val.GetComponentInChildren<MeshFilter>(true); if ((Object)(object)componentInChildren != (Object)null) { Say(args, $"Renderer xform: pos={((Component)componentInChildren).transform.localPosition} rot={((Component)componentInChildren).transform.localEulerAngles} scale={((Component)componentInChildren).transform.localScale}"); Say(args, $"Root scale: {val.transform.localScale}"); if ((Object)(object)componentInChildren.sharedMesh != (Object)null) { string name = ((Object)componentInChildren.sharedMesh).name; object arg2 = componentInChildren.sharedMesh.vertexCount; Bounds bounds = componentInChildren.sharedMesh.bounds; Say(args, $"Mesh '{name}': {arg2} verts, bounds size={((Bounds)(ref bounds)).size}"); } else { Say(args, "Mesh: NULL - the bundle mesh never got assigned."); } } BoxCollider componentInChildren2 = val.GetComponentInChildren<BoxCollider>(true); if ((Object)(object)componentInChildren2 != (Object)null) { Say(args, $"Collider: center={componentInChildren2.center} size={componentInChildren2.size} xformScale={((Component)componentInChildren2).transform.localScale}"); } GameObject val3 = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab("Hammer") : null); PieceTable val4 = ((!((Object)(object)val3 != (Object)null)) ? null : val3.GetComponent<ItemDrop>()?.m_itemData?.m_shared?.m_buildPieces); Say(args, "Hammer PieceTable: " + (((Object)(object)val4 != (Object)null) ? "found" : "NOT FOUND")); if ((Object)(object)val4 != (Object)null) { Say(args, string.Format(" m_pieces count={0}, contains our prefab: {1}", val4.m_pieces.Count, val4.m_pieces.Contains(val) ? "YES" : "NO")); Say(args, " m_categories: " + string.Join(", ", val4.m_categories.Select((PieceCategory c) => ((object)(*(PieceCategory*)(&c))/*cast due to .constrained prefix*/).ToString()).ToArray())); Say(args, $" m_availablePieces buckets={val4.m_availablePieces.Count}"); for (int num = 0; num < val4.m_availablePieces.Count; num++) { bool flag = val4.m_availablePieces[num].Any((Piece x) => (Object)(object)x == (Object)(object)p); Say(args, string.Format(" [{0}] {1}: {2} piece(s){3}", num, (object)(PieceCategory)num, val4.m_availablePieces[num].Count, flag ? " <-- OURS IS HERE" : "")); } } if ((Object)(object)Player.m_localPlayer != (Object)null) { HashSet<string> knownRecipes = Player.m_localPlayer.m_knownRecipes; Say(args, "Player knows recipe '" + p.m_name + "': " + ((knownRecipes != null && knownRecipes.Contains(p.m_name)) ? "YES" : "NO")); Say(args, $"HaveRequirements(IsKnown): {Player.m_localPlayer.HaveRequirements(p, (RequirementMode)1)}"); Say(args, $"HaveRequirements(CanBuild): {Player.m_localPlayer.HaveRequirements(p, (RequirementMode)0)}"); HashSet<string> knownMaterial = Player.m_localPlayer.m_knownMaterial; if (p.m_resources == null) { return; } Requirement[] resources2 = p.m_resources; foreach (Requirement val5 in resources2) { if (!((Object)(object)val5?.m_resItem == (Object)null)) { string name2 = val5.m_resItem.m_itemData.m_shared.m_name; Say(args, " material known '" + name2 + "': " + ((knownMaterial != null && knownMaterial.Contains(name2)) ? "YES" : "NO")); } } } else { Say(args, "No local player - run this from inside a game for the recipe checks."); } } catch (Exception ex) { Say(args, "diagnostics threw: " + ex.Message); Plugin.Log.LogError((object)$"AwayFromHome: afh_diag failed. Reason: {ex}"); } } private static void RunAdminList(ConsoleEventArgs args) { if (!Configuration.IsAdmin) { Say(args, "Admins only."); return; } IReadOnlyList<SiteRecord> allKnownSites = SiteRegistry.GetAllKnownSites(); if (allKnownSites.Count == 0) { Say(args, "No Keeper Stones standing anywhere on this server."); return; } Say(args, $"{allKnownSites.Count} Keeper Stone(s) server-wide:"); for (int i = 0; i < allKnownSites.Count; i++) { SiteRecord siteRecord = allKnownSites[i]; string arg = (string.IsNullOrEmpty(siteRecord.OwnerName) ? "unknown" : siteRecord.OwnerName); Say(args, $" [{i}] {arg}: {siteRecord.Label}"); } Say(args, "Remove one with afh_admin_remove <index>."); } private static void RunAdminRemove(ConsoleEventArgs args) { //IL_00c4: Unknown result type (might be due to invalid IL or missing references) if (!Configuration.IsAdmin) { Say(args, "Admins only."); return; } if (args.Length < 2) { Say(args, "Usage: afh_admin_remove <index>. See afh_admin_list."); return; } if (!int.TryParse(args[1], out var result)) { Say(args, "'" + args[1] + "' is not a number. See afh_admin_list."); return; } IReadOnlyList<SiteRecord> allKnownSites = SiteRegistry.GetAllKnownSites(); if (result < 0 || result >= allKnownSites.Count) { Say(args, $"No stone at index {result} - there are {allKnownSites.Count}. Re-run afh_admin_list."); return; } SiteRecord siteRecord = allKnownSites[result]; SiteRegistry.RequestAdminRemove(siteRecord.Id); Say(args, "Requested removal of the stone at " + siteRecord.Label + "."); } } public static class Configuration { public static ConfigSync configSync = new ConfigSync("wubarrk.AwayFromHome") { DisplayName = "Away From Home", CurrentVersion = "1.0.0", MinimumRequiredVersion = "1.0.0" }; public static ConfigEntry<bool> lockConfiguration; public static ConfigEntry<bool> enabled; public static ConfigEntry<int> ring; public static ConfigEntry<float> scanIntervalSeconds; public static ConfigEntry<float> dwellSeconds; public static ConfigEntry<float> minimumCycleSeconds; public static ConfigEntry<float> settleTimeoutSeconds; public static ConfigEntry<float> settleRadius; public static ConfigEntry<float> claimIntervalSeconds; public static ConfigEntry<int> maxSitesPerPlayer; public static ConfigEntry<bool> adminBypass; public static ConfigEntry<float> livestockLeashMeters; public static ConfigEntry<float> maxLeashMeters; public static ConfigEntry<bool> autoFeed; public static ConfigEntry<bool> restockProduction; public static ConfigEntry<float> productionReachMeters; public static ConfigEntry<bool> creditOfflineProduction; public static ConfigEntry<bool> allowRemoteSites; public static ConfigEntry<bool> verboseLogging; public static ConfigEntry<KeyCode> menuKey; public static ConfigEntry<Color> uiGoldColour; public static bool IsAdmin => configSync.IsAdmin; public static void Init(ConfigFile config) { //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Expected O, but got Unknown //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Expected O, but got Unknown //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Expected O, but got Unknown //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Expected O, but got Unknown //IL_0216: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Expected O, but got Unknown //IL_0289: Unknown result type (might be due to invalid IL or missing references) //IL_0293: Expected O, but got Unknown //IL_02d1: Unknown result type (might be due to invalid IL or missing references) //IL_02db: Expected O, but got Unknown //IL_0307: Unknown result type (might be due to invalid IL or missing references) //IL_0311: Expected O, but got Unknown //IL_033d: Unknown result type (might be due to invalid IL or missing references) //IL_0347: Expected O, but got Unknown //IL_0385: Unknown result type (might be due to invalid IL or missing references) //IL_038f: Expected O, but got Unknown //IL_0463: Unknown result type (might be due to invalid IL or missing references) lockConfiguration = config.Bind<bool>("0 - Server Sync", "LockConfiguration", true, "If true (default), every setting below is locked to the server's value for anyone who is not an admin - the whole point of making this mod server-authoritative. Turn off only for local testing."); configSync.AddLockingConfigEntry<bool>(lockConfiguration); enabled = config.Bind<bool>("1 - General", "Enabled", true, "Master switch. If false, no sites are kept alive - the mod is completely inert."); configSync.AddConfigEntry<bool>(enabled); ring = config.Bind<int>("1 - General", "Ring", 1, new ConfigDescription("How many zones out from a site's centre get loaded and claimed, per axis. 1 = 3x3 zones (192m across), which covers any sane pen or workshop. 2 = 5x5 (320m). Clamped - a wider ring is a guaranteed hitch on any hardware.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 2), Array.Empty<object>())); configSync.AddConfigEntry<int>(ring); scanIntervalSeconds = config.Bind<float>("1 - General", "StoneScanIntervalSeconds", 60f, new ConfigDescription("How often the server re-scans the world for standing Keeper Stones. Placing or destroying one triggers an immediate re-scan anyway, so this only catches changes nothing told the server about - a stone destroyed by a raid, or a world edited offline. The scan walks the whole ZDO index, so on a very large world do not set it aggressively low.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 600f), Array.Empty<object>())); configSync.AddConfigEntry<float>(scanIntervalSeconds); dwellSeconds = config.Bind<float>("2 - Rotation", "DwellSeconds", 180f, new ConfigDescription("How long the keeper holds each site alive per visit, after it settles. Long enough for a smelter/kiln/spinning wheel to drain its full backlog (their production accumulator resets and starts draining the instant the site is owned and loaded - one natural tick is enough) and for a few real taming/love-point ticks to land for animals (those genuinely need loaded+owned wall-clock time, not just a visit). Very small pens/workshops need less; ranches you actually want breeding faster benefit from more.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 300f), Array.Empty<object>())); configSync.AddConfigEntry<float>(dwellSeconds); minimumCycleSeconds = config.Bind<float>("2 - Rotation", "MinimumCycleSeconds", 600f, new ConfigDescription("How long a full rotation must take, at minimum. If a lap of every site finishes sooner, the keeper waits out the difference before starting the next one. This is a SAFETY dial, not a performance one. Each visit loads a site and then unloads it again, and every one of those cycles re-instantiates the buildings and re-seats the animals from their saved positions - which is a chance for a creature standing against a fence to be resolved onto the wrong side of it. Left unpaced, three sites are each loaded and unloaded roughly once a minute, about a thousand times a day, and those chances add up: penned animals drift out over time. Fewer, longer visits give the same loaded wall-clock time with a fraction of the churn. The cap on how high this can go is the smelter forfeiture window - keep the whole cycle under ~55 minutes.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3000f), Array.Empty<object>())); configSync.AddConfigEntry<float>(minimumCycleSeconds); settleTimeoutSeconds = config.Bind<float>("2 - Rotation", "SettleTimeoutSeconds", 20f, "How long the keeper waits for a site's terrain/objects to finish streaming in before giving up on that visit and moving to the next site. A site that times out is retried next rotation - nothing is lost, the visit is just skipped once."); configSync.AddConfigEntry<float>(settleTimeoutSeconds); settleRadius = config.Bind<float>("2 - Rotation", "SettleCheckRadiusMeters", 100f, "Radius, in metres, checked for queued terrain-mesh rebuilds when deciding a site has finished loading. Matches the anchor's own settle test."); configSync.AddConfigEntry<float>(settleRadius); claimIntervalSeconds = config.Bind<float>("2 - Rotation", "ReclaimIntervalSeconds", 5f, "While a site is held, how often (seconds) the keeper re-scans for newly-unowned ZDOs in range and claims them. Claims are polite: a ZDO already owned by a real player, or by another keeper, is never touched."); configSync.AddConfigEntry<float>(claimIntervalSeconds); maxSitesPerPlayer = config.Bind<int>("1 - General", "MaxSitesPerPlayer", 3, new ConfigDescription("How many Keeper Stones one character may have standing at once. 0 = unlimited. This is the mod's real economy: every extra site lengthens the rotation for EVERYONE on the server, and a cycle slower than ~55 minutes starts costing smelters production between visits, so an unlimited server is one player away from making the feature useless for the rest. Enforced twice - the hammer refuses to place past the limit, and the server independently tends only this many per character, so a client running an edited config gains nothing.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 50), Array.Empty<object>())); configSync.AddConfigEntry<int>(maxSitesPerPlayer); adminBypass = config.Bind<bool>("1 - General", "AdminBypass", true, "Whether server admins are exempt from MaxSitesPerPlayer. On by default, matching the rest of this stable - an admin is the person who SETS the economy and routinely needs stones the limit would refuse (test sites, a server hub, a build they are fixing for someone else). This is enforced in both places the cap is: the hammer stops refusing placements, and the server tends every one of an admin's stones instead of the first few. Turn it off for a server where admins are expected to live under the same rule as everyone else. Note the honest limit of the mechanism: which stones are exempt is remembered ON THE STONE, so it survives the admin logging off - which also means a determined client running a patched assembly could stamp its own. MaxSitesPerPlayer is a courtesy dial for keeping the rotation short, not a security boundary, and never was."); configSync.AddConfigEntry<bool>(adminBypass); livestockLeashMeters = config.Bind<float>("2 - Rotation", "LivestockLeashMeters", 12f, new ConfigDescription("The leash a Keeper Stone starts with, in metres, before anyone sets one on it. A new stone starts as a CIRCLE of this radius; its owner can change it to a square or a rectangle at the stone, which is what pens that are not round need. Either way it is centred on the stone, which is why the stone belongs in the MIDDLE of the pen: an animal is judged by its distance from the stone, so an off-centre stone means the far corner of the pen is outside the leash and its occupants get walked back from it forever. 0 means new stones start unleashed. This is only the starting value - each stone carries its own radius, set by its owner at the stone, and MaxLeashMeters is the ceiling. The leash exists because a held site has NO PLAYER in it: creature AI still runs, but with nobody to orient on, animals wander continuously and lean on fence colliders for minutes at a time until they penetrate them - drifts of 80-100m in a single visit have been measured.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 200f), Array.Empty<object>())); configSync.AddConfigEntry<float>(livestockLeashMeters); maxLeashMeters = config.Bind<float>("2 - Rotation", "MaxLeashMeters", 40f, new ConfigDescription("The largest leash any player may set on their own Keeper Stone, measured as distance FROM THE STONE. This is the dial that actually binds, and it is deliberately SEPARATE from LivestockLeashMeters: folding the two together looked tidy and was wrong, because it made the starting value the ceiling as well, so anybody whose pen was larger than the default could not set a leash big enough to fit it and their animals were hauled back from their own fence line forever. Lower it to rein in every site at once - existing stones are clamped to it on read, so it takes effect immediately without editing a single stone. Note what this means for the square and rectangular pens: it bounds each half-extent, so a 40 here allows a pen 80m across, whose CORNER sits about 57m from the stone. That is deliberate rather than an oversight - clamping the corner instead would mean the size a player set and the pen they got were different numbers, which is a far worse thing for them to discover. Keep it comfortably larger than the biggest pen on the server and comfortably smaller than the distance an escaped animal reaches, since everything inside the outline is territory the keeper will not correct.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 200f), Array.Empty<object>())); configSync.AddConfigEntry<float>(maxLeashMeters); autoFeed = config.Bind<bool>("1 - General", "AutoFeed", true, new ConfigDescription("Whether a stocked Keeper Stone puts food out for the animals around it. Valheim only advances taming and breeding while an animal is FED, so on an unattended site this is not a convenience on top of the keeper - it is the thing that lets taming actually finish rather than sitting at the same percentage forever. The stone places ONE real serving on the ground in front of itself and replaces it once it has been eaten, because the game's animals can only ever find food as a physical item in the world; there is no way to make them eat out of a chest, and pretending otherwise would mean inventing meals that never happened. Nothing is spent unless a kept animal within the leash is genuinely hungry, and nothing is put out while a site is still loading. Turn this off to leave feeding entirely to the player, or to another mod.", (AcceptableValueBase)null, Array.Empty<object>())); configSync.AddConfigEntry<bool>(autoFeed); restockProduction = config.Bind<bool>("1 - General", "RestockProduction", true, new ConfigDescription("Whether a stocked Keeper Stone reloads the smelters, kilns, blast furnaces, windmills, spinning wheels and eitr refineries around it from its own six slots. This is the other half of CreditOfflineProduction and the two are only worth much together: that one hands a furnace back the time a frozen server clock owed it, so it genuinely burns through its load while nobody is there - and then stops, because nobody is there to reload it. A full hopper is about seventeen minutes of a rotation that can run for days. The stone walks exactly the path a player's hand walks (the same allowed-item check, then vanilla's own add-ore/add-fuel), so nothing is created, nothing skips a validation, and a furnace that would refuse an item from you refuses it from the stone. While AutoFeed is on, food that animals near the stone eat is never spent this way, so a lox pen beside a windmill does not get its barley ground into flour - turning AutoFeed off makes that stone a pure supply depot and lifts the reservation, which is the right reading of 'this stone does not feed animals'. Turn this off to leave restocking entirely to the player, or to another mod.", (AcceptableValueBase)null, Array.Empty<object>())); configSync.AddConfigEntry<bool>(restockProduction); productionReachMeters = config.Bind<float>("1 - General", "ProductionReachMeters", 24f, new ConfigDescription("How far from a Keeper Stone its six slots will reload production pieces, in metres. 0 switches restocking off by radius alone. Deliberately much smaller than a site (which is 192m across) and deliberately NOT tied to the leash: the leash is a pen for animals and this is an arm's reach for a workshop, and a player who wants a big pen has no reason to also want their stone feeding a neighbour's furnaces on the far side of the site. Capped below one zone (64m) because the scan for producers only ever looks at the stone's own zone and its eight neighbours, so a larger number here would silently find nothing beyond that.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 48f), Array.Empty<object>())); configSync.AddConfigEntry<float>(productionReachMeters); creditOfflineProduction = config.Bind<bool>("2 - Rotation", "CreditOfflineProduction", true, "Whether smelters, kilns, blast furnaces, spinning wheels and windmills at a kept site are given the time a DEDICATED SERVER'S FROZEN CLOCK owed them. Valheim's server stops advancing world time entirely while no players are connected (ZNet.UpdateNetTime returns early on zero players), and every one of those machines measures its progress against that clock - so on an empty server they do not advance by a single second no matter who owns them or how long the keeper holds the site. With this on, each machine is given the real elapsed time since the keeper last tended it, and vanilla's own production code does the rest under its own rules, including its one-hour-per-gap ceiling. Nothing global is touched: the world clock, day/night and everything else stay exactly as Valheim intends. Turn this off for strictly vanilla timing, and accept that production stops whenever the server is empty. Has no effect on a server with players on it, where the clock runs normally and the correction computes to zero by itself."); configSync.AddConfigEntry<bool>(creditOfflineProduction); allowRemoteSites = config.Bind<bool>("3 - Dedicated Server", "AllowRemoteSites", true, "Server-side authority. If this client is a pure client of a DEDICATED server (not the host), sites more than one active-area away need the server to stream their sectors on request. Set false on the server to refuse that streaming entirely - every connected client's Away From Home then only reaches sites inside their own normal view range, same as vanilla."); configSync.AddConfigEntry<bool>(allowRemoteSites); verboseLogging = config.Bind<bool>("9 - Debug", "VerboseLogging", false, "Logs every settle/claim/hold step at Info level instead of just rotation summaries. Useful for diagnosing a site that never seems to settle."); configSync.AddConfigEntry<bool>(verboseLogging); menuKey = config.Bind<KeyCode>("8 - Menu", "MenuKey", (KeyCode)288, "Opens/closes the ADMIN panel - every Keeper Stone on the server, with the power to remove any of them. Does nothing if you are not an admin: players mark a site by building a Keeper Stone and read it by looking at it, so there is no player menu to open. Purely local - never locked, never synced."); uiGoldColour = config.Bind<Color>("8 - Menu", "MenuAccentColour", new Color(0.8f, 0.62f, 0.26f, 1f), "The menu's metal accent colour. Purely local cosmetics."); } } public static class IconRenderer { private const int Layer = 30; private const int Size = 128; private const float MinCoverage = 0.02f; private const float MinLuminance = 0.02f; public static float LastCoverage { get; private set; } public static float LastLuminance { get; private set; } public static string LastPath { get; private set; } = "not attempted"; public static Sprite Render(Mesh mesh, Material material) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Invalid comparison between Unknown and I4 if ((Object)(object)mesh == (Object)null) { LastPath = "no mesh"; return null; } if ((int)SystemInfo.graphicsDeviceType == 4) { LastPath = "headless - skipped"; return null; } Sprite val = TryRender(mesh, material, "piece material"); if ((Object)(object)val != (Object)null) { return val; } Material val2 = BuildUnlitMaterial(material); if ((Object)(object)val2 != (Object)null) { val = TryRender(mesh, val2, "unlit fallback"); if ((Object)(object)val != (Object)null) { return val; } } return null; } private static Material BuildUnlitMaterial(Material source) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown string[] array = new string[4] { "Unlit/Texture", "Legacy Shaders/Diffuse", "Sprites/Default", "Standard" }; foreach (string text in array) { Shader val = Shader.Find(text); if ((Object)(object)val == (Object)null) { continue; } Material val2 = new Material(val) { name = "AFH_IconFallback" }; if ((Object)(object)source != (Object)null) { string[] array2 = new string[4] { "_MainTex", "_BaseMap", "_MainTexture", "_BaseColorTexture" }; foreach (string text2 in array2) { if (!source.HasProperty(text2)) { continue; } Texture texture = source.GetTexture(text2); if (!((Object)(object)texture == (Object)null)) { if (val2.HasProperty("_MainTex")) { val2.SetTexture("_MainTex", texture); } else if (val2.HasProperty("_BaseMap")) { val2.SetTexture("_BaseMap", texture); } break; } } } return val2; } return null; } private static Sprite TryRender(Mesh mesh, Material material, string label) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Expected O, but got Unknown //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Expected O, but got Unknown //IL_01e7: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_0210: Unknown result type (might be due to invalid IL or missing references) //IL_0215: Unknown result type (might be due to invalid IL or missing references) //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_022b: Unknown result type (might be due to invalid IL or missing references) //IL_0253: Unknown result type (might be due to invalid IL or missing references) //IL_0262: Unknown result type (might be due to invalid IL or missing references) //IL_0301: Unknown result type (might be due to invalid IL or missing references) //IL_0310: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)material == (Object)null) { return null; } GameObject val = null; GameObject val2 = null; GameObject val3 = null; RenderTexture val4 = null; RenderTexture active = RenderTexture.active; Texture2D val5 = null; Texture2D val6 = null; try { Bounds bounds = mesh.bounds; Vector3 extents = ((Bounds)(ref bounds)).extents; float num = Mathf.Max(((Vector3)(ref extents)).magnitude, 0.01f); val2 = new GameObject("AFH_IconModel") { layer = 30 }; val2.AddComponent<MeshFilter>().sharedMesh = mesh; MeshRenderer val7 = val2.AddComponent<MeshRenderer>(); ((Renderer)val7).sharedMaterial = material; ((Renderer)val7).shadowCastingMode = (ShadowCastingMode)0; ((Renderer)val7).receiveShadows = false; val2.transform.position = new Vector3(0f, 20000f, 0f) - ((Bounds)(ref bounds)).center; val2.transform.rotation = Quaternion.Euler(0f, 30f, 0f); val3 = new GameObject("AFH_IconLight") { layer = 30 }; Light val8 = val3.AddComponent<Light>(); val8.type = (LightType)1; val8.intensity = 1.25f; val8.cullingMask = 1073741824; val3.transform.rotation = Quaternion.Euler(35f, -35f, 0f); Vector3 val9 = default(Vector3); ((Vector3)(ref val9))..ctor(0f, 20000f, 0f); val = new GameObject("AFH_IconCam") { layer = 30 }; Camera val10 = val.AddComponent<Camera>(); val10.orthographic = true; val10.orthographicSize = num * 1.05f; val10.cullingMask = 1073741824; val10.clearFlags = (CameraClearFlags)2; val10.nearClipPlane = 0.01f; val10.farClipPlane = num * 8f; ((Behaviour)val10).enabled = false; val10.renderingPath = (RenderingPath)1; val10.allowHDR = false; val10.allowMSAA = false; val.transform.position = val9 + Quaternion.Euler(12f, 0f, 0f) * new Vector3(0f, 0f, (0f - num) * 3f); val.transform.LookAt(val9); val4 = (val10.targetTexture = RenderTexture.GetTemporary(128, 128, 24, (RenderTextureFormat)0, (RenderTextureReadWrite)2)); val5 = Shoot(val10, val4, Color.black); val6 = Shoot(val10, val4, Color.white); Texture2D val11 = Compose(val5, val6); if ((Object)(object)val11 == (Object)null) { Plugin.Log.LogWarning((object)$"AwayFromHome: icon render via {label} drew {LastCoverage:P1} coverage at {LastLuminance:F3} luminance - rejecting it."); return null; } LastPath = label; Plugin.Log.LogInfo((object)$"AwayFromHome: rendered the Keeper Stone's icon via {label} ({LastCoverage:P1} coverage, {LastLuminance:F2} luminance)."); Sprite val12 = Sprite.Create(val11, new Rect(0f, 0f, 128f, 128f), new Vector2(0.5f, 0.5f)); ((Object)val12).hideFlags = (HideFlags)61; return val12; } catch (Exception arg) { Plugin.Log.LogError((object)$"AwayFromHome: rendering the Keeper Stone's icon via {label} failed (non-fatal). Reason: {arg}"); return null; } finally { RenderTexture.active = active; if ((Object)(object)val4 != (Object)null) { RenderTexture.ReleaseTemporary(val4); } if ((Object)(object)val5 != (Object)null) { Object.DestroyImmediate((Object)(object)val5); } if ((Object)(object)val6 != (Object)null) { Object.DestroyImmediate((Object)(object)val6); } if ((Object)(object)val != (Object)null) { Object.DestroyImmediate((Object)(object)val); } if ((Object)(object)val2 != (Object)null) { Object.DestroyImmediate((Object)(object)val2); } if ((Object)(object)val3 != (Object)null) { Object.DestroyImmediate((Object)(object)val3); } } } private static Texture2D Shoot(Camera cam, RenderTexture rt, Color background) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown //IL_005a: Unknown result type (might be due to invalid IL or missing references) cam.backgroundColor = new Color(background.r, background.g, background.b, 1f); cam.Render(); RenderTexture.active = rt; Texture2D val = new Texture2D(128, 128, (TextureFormat)4, false, false); val.ReadPixels(new Rect(0f, 0f, 128f, 128f), 0, 0); val.Apply(false, false); return val; } private static Texture2D Compose(Texture2D onBlack, Texture2D onWhite) { //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0215: Unknown result type (might be due to i