Decompiled source of Serverbound v0.1.2

BepInEx/plugins/Serverbound/Serverbound.dll

Decompiled 7 hours ago
#define TRACE
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Mono.Cecil.Cil;
using MonoMod.Cil;
using Serverbound.Compatibility;
using Serverbound.FeatureModel;
using Serverbound.Features;
using Serverbound.Patching;
using Serverbound.Requirements;
using Serverbound.Settings;
using SoftReferenceableAssets;
using Steamworks;
using Unity.Jobs.LowLevel.Unsafe;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: ComVisible(false)]
[assembly: InternalsVisibleTo("Serverbound.Tests")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: IgnoresAccessChecksTo("SoftReferenceableAssets")]
[assembly: AssemblyCompany("Serverbound")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Dedicated world simulation and optional client compatibility for Valheim.")]
[assembly: AssemblyFileVersion("0.1.2.0")]
[assembly: AssemblyInformationalVersion("0.1.2")]
[assembly: AssemblyProduct("Serverbound")]
[assembly: AssemblyTitle("Serverbound")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.2.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace Serverbound
{
	public static class AdminCommands
	{
		private static Dictionary<string, GameObject> s_items;

		public static ZNetPeer FindPlayer(string name, out string problem)
		{
			problem = null;
			List<ZNetPeer> list = (from p in ZNet.instance.GetPeers()
				where p.IsReady() && !string.IsNullOrEmpty(p.m_playerName)
				select p).ToList();
			ZNetPeer val = ((IEnumerable<ZNetPeer>)list).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer p) => string.Equals(p.m_playerName, name, StringComparison.OrdinalIgnoreCase)));
			if (val != null)
			{
				return val;
			}
			List<ZNetPeer> list2 = list.Where((ZNetPeer p) => p.m_playerName.StartsWith(name, StringComparison.OrdinalIgnoreCase)).ToList();
			List<ZNetPeer> list3 = ((list2.Count > 0) ? list2 : list.Where((ZNetPeer p) => p.m_playerName.IndexOf(name, StringComparison.OrdinalIgnoreCase) >= 0).ToList());
			if (list3.Count == 1)
			{
				return list3[0];
			}
			problem = ((list3.Count == 0) ? ("No player '" + name + "' online. Online: " + Names(list)) : ("'" + name + "' matches several players: " + Names(list3)));
			return null;
		}

		public static string Players()
		{
			List<ZNetPeer> list = (from p in ZNet.instance.GetPeers()
				where p.IsReady()
				select p).ToList();
			if (list.Count == 0)
			{
				return "No players online";
			}
			return string.Join("; ", list.Select(delegate(ZNetPeer p)
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				//IL_002b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0039: Unknown result type (might be due to invalid IL or missing references)
				Vector3 refPos = p.GetRefPos();
				return $"{p.m_playerName} ({p.m_socket.GetHostName()}) at {refPos.x:0},{refPos.z:0}";
			}));
		}

		public static string Give(ZNetPeer target, string itemName, int amount, string byWhom)
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: 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_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: 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_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: 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_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = FindItem(itemName);
			if (!Object.op_Implicit((Object)(object)val))
			{
				return "No item '" + itemName + "'." + Suggest(itemName);
			}
			amount = Mathf.Clamp(amount, 1, Mathf.Max(1, Configuration.maxGiveAmount.Value));
			ZDO zDO = ZDOMan.instance.GetZDO(target.m_characterID);
			Vector3 val2 = ((zDO != null) ? zDO.GetPosition() : target.GetRefPos()) + Vector3.up * 1.5f;
			int num = Mathf.Max(1, val.GetComponent<ItemDrop>().m_itemData.m_shared.m_maxStackSize);
			int num2 = 0;
			for (int num3 = amount; num3 > 0; num3 -= num)
			{
				Vector2 val3 = Random.insideUnitCircle * 0.75f;
				Vector3 val4 = val2 + new Vector3(val3.x, 0.25f * (float)num2, val3.y);
				Object.Instantiate<GameObject>(val, val4, Quaternion.identity).GetComponent<ItemDrop>().SetStack(Mathf.Min(num3, num));
				num2++;
			}
			string text = $"{byWhom} gave {amount} x {((Object)val).name} to {target.m_playerName} in {num2} stack(s) at {val2.x:0},{val2.z:0}";
			Plugin.logger.LogInfo((object)("Admin: " + text));
			return text;
		}

		public static string Save(string byWhom)
		{
			if (!Object.op_Implicit((Object)(object)ZNet.instance) || !ZNet.instance.IsServer())
			{
				return "No world loaded, nothing to save";
			}
			bool flag = default(bool);
			if (!ZNet.instance.EnoughDiskSpaceAvailable(ref flag, false, (Action<bool>)null))
			{
				return "Not enough disk space, world not saved";
			}
			Plugin.logger.LogInfo((object)("Admin: " + byWhom + " requested a world save"));
			ZNet.instance.Save(false, true, true);
			return "Saving world";
		}

		public static string Broadcast(string text, string byWhom)
		{
			ZRoutedRpc.instance.InvokeRoutedRPC(0L, "ShowMessage", new object[2] { 2, text });
			Plugin.logger.LogInfo((object)("Admin: " + byWhom + " broadcast: " + text));
			return $"shown to {ZNet.instance.GetNrOfPlayers()} player(s): {text}";
		}

		public static string StartEvent(string name, ZNetPeer target, string byWhom)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: 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_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)RandEventSystem.instance) || !RandEventSystem.instance.HaveEvent(name))
			{
				return "No event '" + name + "'. Events: " + Events();
			}
			ZDO zDO = ZDOMan.instance.GetZDO(target.m_characterID);
			Vector3 val = ((zDO != null) ? zDO.GetPosition() : target.GetRefPos());
			RandEventSystem.instance.SetRandomEventByName(name, val);
			string text = $"{byWhom} started event {name} at {target.m_playerName} ({val.x:0},{val.z:0})";
			Plugin.logger.LogInfo((object)("Admin: " + text));
			return text;
		}

		public static string Events()
		{
			if (!Object.op_Implicit((Object)(object)RandEventSystem.instance))
			{
				return "none (no world loaded)";
			}
			return string.Join(", ", RandEventSystem.instance.m_events.Select((RandomEvent e) => e.m_name));
		}

		public static bool TryParseAmount(string text, out int amount)
		{
			return int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out amount);
		}

		private static string Names(List<ZNetPeer> peers)
		{
			if (peers.Count != 0)
			{
				return string.Join(", ", peers.Select((ZNetPeer p) => p.m_playerName));
			}
			return "nobody";
		}

		private static GameObject FindItem(string name)
		{
			if (s_items == null)
			{
				s_items = new Dictionary<string, GameObject>(StringComparer.OrdinalIgnoreCase);
				foreach (GameObject prefab in ZNetScene.instance.m_prefabs)
				{
					if (Object.op_Implicit((Object)(object)prefab) && Object.op_Implicit((Object)(object)prefab.GetComponent<ItemDrop>()) && !s_items.ContainsKey(((Object)prefab).name))
					{
						s_items[((Object)prefab).name] = prefab;
					}
				}
			}
			if (!s_items.TryGetValue(name, out var value))
			{
				return null;
			}
			return value;
		}

		private static string Suggest(string name)
		{
			List<string> list = new List<string>();
			foreach (string key in s_items.Keys)
			{
				if (key.IndexOf(name, StringComparison.OrdinalIgnoreCase) >= 0)
				{
					list.Add(key);
					if (list.Count == 8)
					{
						break;
					}
				}
			}
			if (list.Count <= 0)
			{
				return "";
			}
			return " Similar: " + string.Join(", ", list);
		}
	}
	public static class ConsoleInput
	{
		public delegate void LineHandler(string line, string notice);

		private static readonly UTF8Encoding s_utf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);

		private const string Cp1250 = "€\ufffd‚\ufffd„…†‡\ufffd‰Š‹ŚŤŽŹ\ufffd‘’“”•–—\ufffd™š›śťžź\u00a0ˇ\u02d8٤Ą¦§\u00a8©Ş«¬\u00ad®Ż°±\u02dbł\u00b4µ¶·\u00b8ąş»Ľ\u02ddľżŔÁÂĂÄĹĆÇČÉĘËĚÍÎĎĐŃŇÓÔŐÖ×ŘŮÚŰÜÝŢßŕáâăäĺćçčéęëěíîďđńňóôőö÷řůúűüýţ\u02d9";

		private const string Cp852 = "ÇüéâäůćçłëŐőîŹÄĆÉĹĺôöĽľŚśÖÜŤťŁ×čáíóúĄąŽžĘ꬟Ⱥ«»░▒▓│┤ÁÂĚŞ╣║╗╝Żż┐└┴┬├─┼Ăă╚╔╩╦╠═╬¤đĐĎËďŇÍÎě┘┌█▄ŢŮ▀ÓßÔŃńňŠšŔÚŕŰýÝţ\u00b4\u00ad\u02dd\u02dbˇ\u02d8§÷\u00b8°\u00a8\u02d9űŘř■\u00a0";

		public static void ReadLines(Func<int> codePage, LineHandler handler)
		{
			bool noticed = false;
			using Stream stream = Console.OpenStandardInput();
			byte[] array = new byte[4096];
			List<byte> list = new List<byte>();
			int num;
			while ((num = stream.Read(array, 0, array.Length)) > 0)
			{
				for (int i = 0; i < num; i++)
				{
					if (array[i] == 10)
					{
						Emit(list, codePage(), handler, ref noticed);
						list.Clear();
					}
					else
					{
						list.Add(array[i]);
					}
				}
			}
			Emit(list, codePage(), handler, ref noticed);
		}

		private static void Emit(List<byte> line, int codePage, LineHandler handler, ref bool noticed)
		{
			if (line.Count > 0 && line[line.Count - 1] == 13)
			{
				line.RemoveAt(line.Count - 1);
			}
			if (line.Count != 0)
			{
				byte[] array = line.ToArray();
				string notice = null;
				bool fellBack;
				string line2 = Decode(array, codePage, out fellBack);
				if (fellBack && !noticed)
				{
					noticed = true;
					notice = string.Format("Console input is not UTF-8 (bytes: {0}); decoded with code page {1}.", BitConverter.ToString(array).Replace("-", " "), codePage) + " If accents come out wrong, set [Server] ConsoleInputCodePage to what the panel sends (1250, 852, 1252 or 65001).";
				}
				handler(line2, notice);
			}
		}

		public static string Decode(byte[] data, int codePage, out bool fellBack)
		{
			fellBack = false;
			bool flag = true;
			for (int i = 0; i < data.Length; i++)
			{
				if (data[i] >= 128)
				{
					flag = false;
					break;
				}
			}
			if (flag)
			{
				return Encoding.ASCII.GetString(data);
			}
			if (codePage != 65001)
			{
				try
				{
					return s_utf8.GetString(data);
				}
				catch (DecoderFallbackException)
				{
					fellBack = true;
				}
			}
			return SingleByte(data, codePage);
		}

		private static string SingleByte(byte[] data, int codePage)
		{
			string text = codePage switch
			{
				1250 => "€\ufffd‚\ufffd„…†‡\ufffd‰Š‹ŚŤŽŹ\ufffd‘’“”•–—\ufffd™š›śťžź\u00a0ˇ\u02d8٤Ą¦§\u00a8©Ş«¬\u00ad®Ż°±\u02dbł\u00b4µ¶·\u00b8ąş»Ľ\u02ddľżŔÁÂĂÄĹĆÇČÉĘËĚÍÎĎĐŃŇÓÔŐÖ×ŘŮÚŰÜÝŢßŕáâăäĺćçčéęëěíîďđńňóôőö÷řůúűüýţ\u02d9", 
				852 => "ÇüéâäůćçłëŐőîŹÄĆÉĹĺôöĽľŚśÖÜŤťŁ×čáíóúĄąŽžĘ꬟Ⱥ«»░▒▓│┤ÁÂĚŞ╣║╗╝Żż┐└┴┬├─┼Ăă╚╔╩╦╠═╬¤đĐĎËďŇÍÎě┘┌█▄ŢŮ▀ÓßÔŃńňŠšŔÚŕŰýÝţ\u00b4\u00ad\u02dd\u02dbˇ\u02d8§÷\u00b8°\u00a8\u02d9űŘř■\u00a0", 
				_ => null, 
			};
			char[] array = new char[data.Length];
			for (int i = 0; i < data.Length; i++)
			{
				byte b = data[i];
				array[i] = (char)((b < 128) ? b : (((int?)text?[b - 128]) ?? ((int)b)));
			}
			return new string(array);
		}
	}
	[Harmony]
	[BepInPlugin("org.serverbound.valheim", "Serverbound", "0.1.2")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInIncompatibility("MVP.Valheim_Serverside_Simulations")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGUID = "org.serverbound.valheim";

		public const string PluginName = "Serverbound";

		public const string PluginVersion = "0.1.2";

		private static Plugin context;

		public static Configuration configuration;

		public static Harmony harmony;

		public const string ValheimPlusPluginId = "org.bepinex.plugins.valheim_plus";

		public static ManualLogSource logger;

		private static bool consoleStarted;

		private static bool installed;

		public static Plugin instance => context;

		private void Awake()
		{
			//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b8: Expected O, but got Unknown
			context = this;
			logger = ((BaseUnityPlugin)this).Logger;
			Configuration.Load(((BaseUnityPlugin)this).Config);
			CompatibilityInstaller.Info = delegate(string message)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)message);
			};
			CompatibilityInstaller.Warning = delegate(string message)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)message);
			};
			CompatibilityInstaller.Error = delegate(string message)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)message);
			};
			try
			{
				CompatibilityInstaller.CheckInstallation(Resolve, VersionOf, IsDedicated());
				Guard.Build(typeof(ZNet).Assembly, IsDedicated() ? ExpectedBuilds.Server : ExpectedBuilds.Valheim);
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Serverbound: REJECTED BUILD/INSTALLATION; no patches installed. " + ex));
				return;
			}
			if (!ModIsEnabled())
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Serverbound is disabled. (configuration)");
				return;
			}
			if (!IsDedicated())
			{
				if (((BaseUnityPlugin)this).Config.Bind<bool>("Compatibility", "ClientSkills", true, "Publish ImpactfulSkills state only after a Serverbound server handshake. Dedicated simulation never runs on clients.").Value)
				{
					CompatibilityInstaller.Install(Resolve, CompatibilityInstaller.Info, CompatibilityInstaller.Warning, CompatibilityInstaller.Error);
				}
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Serverbound client compatibility loaded; dedicated simulation is inactive on this client.");
				return;
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Installing Serverbound");
			LimitJobWorkers(Configuration.unityJobWorkers.Value);
			LimitPhysicsCatchUp(Configuration.maxCatchUpMs.Value);
			if (Configuration.consoleCommandsEnabled.Value)
			{
				ServerConsole.Start();
				consoleStarted = true;
			}
			harmony = new Harmony("org.serverbound.valheim");
			AvailableFeatures availableFeatures = new AvailableFeatures();
			availableFeatures.AddFeature(new Core());
			availableFeatures.AddFeature(new MaxObjectsPerFrame());
			availableFeatures.AddFeature(new Networking());
			availableFeatures.AddFeature(new Performance());
			availableFeatures.AddFeature(new Fixes());
			availableFeatures.AddFeature(new Debugging());
			availableFeatures.AddFeature(new Compat_ValheimCommunityPatch());
			availableFeatures.AddFeature(new DungeonLoadGuard());
			PatchRequirements patchRequirements = new PatchRequirements();
			patchRequirements.AddRequirement(new PatchRequirement.DebugBuild());
			if (PatchFeatures(availableFeatures, new HarmonyFeaturesPatcher(patchRequirements)))
			{
				VanillaDrift.Check(((BaseUnityPlugin)this).Logger);
				installed = true;
				CompatibilityInstaller.Install(Resolve, CompatibilityInstaller.Info, CompatibilityInstaller.Warning, CompatibilityInstaller.Error);
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Serverbound installed; deferred compatibility verification pending.");
			}
		}

		private static Assembly Resolve(string guid)
		{
			if (!Chainloader.PluginInfos.TryGetValue(guid, out var value))
			{
				return null;
			}
			return ((object)value.Instance)?.GetType().Assembly;
		}

		private static Version VersionOf(string guid)
		{
			if (!Chainloader.PluginInfos.TryGetValue(guid, out var value))
			{
				return null;
			}
			return value.Metadata.Version;
		}

		private void Start()
		{
			try
			{
				CompatibilityInstaller.CheckInstallation(Resolve, VersionOf, IsDedicated());
				CompatibilityInstaller.Verify();
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Serverbound: deferred compatibility gate failed. " + ex));
				DisableSimulation();
			}
		}

		internal static void DisableSimulation()
		{
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			installed = false;
			SimulationPatch.Deactivate();
			try
			{
				string[] array = (from p in Harmony.GetAllPatchedMethods().SelectMany(SimulationPatch.All)
					select p.owner).Where(IsSimulationOwner).Distinct().ToArray();
				for (int num = 0; num < array.Length; num++)
				{
					new Harmony(array[num]).UnpatchSelf();
				}
				if (Harmony.GetAllPatchedMethods().SelectMany(SimulationPatch.All).Any((Patch p) => IsSimulationOwner(p.owner)))
				{
					throw new InvalidOperationException("Serverbound hooks remain installed.");
				}
				SimulationPatch.RestoreVendors();
				CompatibilityInstaller.Error("Simulation: SERVERBOUND DISABLED; resolve the rejected integration before admitting players.");
			}
			catch (Exception ex)
			{
				CompatibilityInstaller.Error("Simulation: rollback could not disable Serverbound; stop the server before players join. " + ex);
			}
		}

		private static bool IsSimulationOwner(string owner)
		{
			if (owner == "org.serverbound.valheim" || owner.StartsWith("org.serverbound.valheim.", StringComparison.Ordinal))
			{
				return owner != "org.serverbound.valheim.ServerConsole";
			}
			return false;
		}

		private void Update()
		{
			if (consoleStarted)
			{
				ServerConsole.ProcessPending();
			}
			if (installed)
			{
				PerformanceStats.Frame();
			}
		}

		private void FixedUpdate()
		{
			if (installed)
			{
				PerformanceStats.FixedStep();
			}
		}

		private void LimitJobWorkers(int limit)
		{
			int jobWorkerCount = JobsUtility.JobWorkerCount;
			if (limit > 0 && limit < jobWorkerCount)
			{
				JobsUtility.JobWorkerCount = limit;
				((BaseUnityPlugin)this).Logger.LogInfo((object)$"Unity job worker threads: {jobWorkerCount} -> {JobsUtility.JobWorkerCount}");
			}
		}

		private void LimitPhysicsCatchUp(int milliseconds)
		{
			if (milliseconds > 0)
			{
				float maximumDeltaTime = Time.maximumDeltaTime;
				Time.maximumDeltaTime = Mathf.Max((float)milliseconds / 1000f, Time.fixedDeltaTime);
				((BaseUnityPlugin)this).Logger.LogInfo((object)($"Physics catch-up: at most {Mathf.RoundToInt(Time.maximumDeltaTime / Time.fixedDeltaTime)} fixed steps per frame " + $"(fixed step {1000f * Time.fixedDeltaTime:0} ms, longest frame counted {1000f * maximumDeltaTime:0} -> {1000f * Time.maximumDeltaTime:0} ms)"));
			}
		}

		private bool PatchFeatures(AvailableFeatures availableFeatures, HarmonyFeaturesPatcher patcher)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected O, but got Unknown
			List<Harmony> list = new List<Harmony>();
			foreach (IFeature item in availableFeatures.EnabledFeatures())
			{
				string name = item.GetType().Name;
				Harmony val = new Harmony("org.serverbound.valheim." + name);
				try
				{
					patcher.PatchAll(item.GetType().GetNestedTypes(), val);
					list.Add(val);
				}
				catch (Exception arg)
				{
					val.UnpatchSelf();
					if (item is Core)
					{
						((BaseUnityPlugin)this).Logger.LogError((object)string.Format("Core patches failed to apply; {0} is disabled and the server runs vanilla. {1}", "Serverbound", arg));
						foreach (Harmony item2 in list)
						{
							item2.UnpatchSelf();
						}
						harmony.UnpatchSelf();
						return false;
					}
					((BaseUnityPlugin)this).Logger.LogError((object)$"Feature {name} failed to apply and is disabled. {arg}");
				}
			}
			return true;
		}

		public bool ModIsEnabled()
		{
			return Configuration.modEnabled.Value;
		}

		public static bool IsDedicated()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			return new ZNet().IsDedicated();
		}
	}
	public static class ServerConsole
	{
		private const string Commands = "save | stop | players | give <item> <amount> <player> | broadcast <text> | event <name> <player> | events | help -- anything else goes to the game console: kick, ban, unban, banned, stopevent, devcommands, skiptime ...";

		private static readonly ConcurrentQueue<string> s_commands = new ConcurrentQueue<string>();

		private static bool s_capturing;

		private static int s_captured;

		private static Harmony s_capture;

		public static void Start()
		{
			Thread thread = new Thread(ReadLoop);
			thread.IsBackground = true;
			thread.Name = "Serverbound console";
			thread.Start();
			Plugin.logger.LogInfo((object)"Console commands enabled on standard input: save | stop | players | give <item> <amount> <player> | broadcast <text> | event <name> <player> | events | help -- anything else goes to the game console: kick, ban, unban, banned, stopevent, devcommands, skiptime ...");
		}

		private static void ReadLoop()
		{
			try
			{
				ConsoleInput.ReadLines(() => Configuration.consoleInputCodePage.Value, delegate(string line, string notice)
				{
					if (notice != null)
					{
						s_commands.Enqueue("\u0001" + notice);
					}
					line = line.Trim();
					if (line.Length > 0)
					{
						s_commands.Enqueue(line);
					}
				});
			}
			catch (Exception ex)
			{
				s_commands.Enqueue("\0" + ex.Message);
			}
		}

		private static void Reply(string text)
		{
			Plugin.logger.LogInfo((object)("Console: " + text));
			WriteOut(text);
		}

		private static void WriteOut(string text)
		{
			try
			{
				Console.Out.WriteLine("Console: " + text);
				Console.Out.Flush();
			}
			catch (Exception)
			{
			}
		}

		public static void ProcessPending()
		{
			string result;
			while (s_commands.TryDequeue(out result))
			{
				Execute(result);
			}
		}

		private static void Execute(string command)
		{
			if (command[0] == '\0')
			{
				Plugin.logger.LogWarning((object)("Console commands unavailable, cannot read standard input: " + command.Substring(1)));
				return;
			}
			if (command[0] == '\u0001')
			{
				Plugin.logger.LogWarning((object)command.Substring(1));
				WriteOut(command.Substring(1));
				return;
			}
			string[] array = command.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
			bool flag = Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.IsServer();
			try
			{
				switch (array[0].ToLowerInvariant())
				{
				case "save":
					Reply(AdminCommands.Save("console"));
					break;
				case "stop":
				case "quit":
				case "shutdown":
					Reply("saving world and shutting down");
					Application.Quit();
					break;
				case "players":
					Reply(flag ? AdminCommands.Players() : "no world loaded");
					break;
				case "give":
				{
					if (!flag)
					{
						Reply("no world loaded");
						break;
					}
					if (array.Length < 4 || !AdminCommands.TryParseAmount(array[2], out var amount))
					{
						Reply("usage: give <item> <amount> <player>, e.g. give Copper 120 Ulf");
						break;
					}
					string problem;
					ZNetPeer val = AdminCommands.FindPlayer(string.Join(" ", array, 3, array.Length - 3), out problem);
					Reply((val == null) ? problem : AdminCommands.Give(val, array[1], amount, "console"));
					break;
				}
				case "broadcast":
				case "say":
					if (!flag)
					{
						Reply("no world loaded");
					}
					else if (array.Length < 2)
					{
						Reply("usage: broadcast <text>");
					}
					else
					{
						Reply(AdminCommands.Broadcast(command.Substring(array[0].Length).Trim(), "console"));
					}
					break;
				case "event":
				{
					if (!flag)
					{
						Reply("no world loaded");
						break;
					}
					if (array.Length < 3)
					{
						Reply("usage: event <name> <player>, e.g. event army_eikthyr Ulf; `events` lists the names");
						break;
					}
					string problem2;
					ZNetPeer val2 = AdminCommands.FindPlayer(string.Join(" ", array, 2, array.Length - 2), out problem2);
					Reply((val2 == null) ? problem2 : AdminCommands.StartEvent(array[1], val2, "console"));
					break;
				}
				case "events":
					Reply(flag ? ("events: " + AdminCommands.Events()) : "no world loaded");
					break;
				case "help":
					Reply("commands: save | stop | players | give <item> <amount> <player> | broadcast <text> | event <name> <player> | events | help -- anything else goes to the game console: kick, ban, unban, banned, stopevent, devcommands, skiptime ...");
					break;
				default:
					RunGameCommand(command, array[0]);
					break;
				}
			}
			catch (Exception ex)
			{
				Plugin.logger.LogWarning((object)$"Console: '{command}' failed: {ex}");
				Reply("'" + command + "' failed: " + ex.Message);
			}
		}

		private static void RunGameCommand(string command, string word)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Expected O, but got Unknown
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Expected O, but got Unknown
			if (!Object.op_Implicit((Object)(object)Console.instance))
			{
				Reply("'" + word + "' is not one of ours and the game console is not up yet. Commands: save | stop | players | give <item> <amount> <player> | broadcast <text> | event <name> <player> | events | help -- anything else goes to the game console: kick, ban, unban, banned, stopevent, devcommands, skiptime ...");
				return;
			}
			if (s_capture == null)
			{
				s_capture = new Harmony("org.serverbound.valheim.ServerConsole");
				s_capture.Patch((MethodBase)AccessTools.Method(typeof(Terminal), "AddString", new Type[1] { typeof(string) }, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(ServerConsole), "AddStringPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			s_capturing = true;
			s_captured = 0;
			try
			{
				((Terminal)Console.instance).TryRunCommand(command, false, true);
			}
			finally
			{
				s_capturing = false;
			}
			if (s_captured == 0)
			{
				Reply("game console ran '" + word + "' without output");
			}
		}

		public static void AddStringPostfix(Terminal __instance, string text)
		{
			if (s_capturing && (Object)(object)__instance == (Object)(object)Console.instance)
			{
				s_captured++;
				WriteOut(text);
			}
		}
	}
	public static class ServerSafe
	{
		private static readonly Dictionary<string, MethodInfo> Replacements = new Dictionary<string, MethodInfo>
		{
			{
				"GetZDOID",
				AccessTools.Method(typeof(ServerSafe), "LocalPlayerZDOID", (Type[])null, (Type[])null)
			},
			{
				"GetPlayerID",
				AccessTools.Method(typeof(ServerSafe), "LocalPlayerID", (Type[])null, (Type[])null)
			},
			{
				"GetPlayerName",
				AccessTools.Method(typeof(ServerSafe), "LocalPlayerName", (Type[])null, (Type[])null)
			}
		};

		public static ZDOID LocalPlayerZDOID()
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)Player.m_localPlayer))
			{
				return ZDOID.None;
			}
			return ((Character)Player.m_localPlayer).GetZDOID();
		}

		public static long LocalPlayerID()
		{
			if (!Object.op_Implicit((Object)(object)Player.m_localPlayer))
			{
				return 0L;
			}
			return Player.m_localPlayer.GetPlayerID();
		}

		public static string LocalPlayerName()
		{
			if (!Object.op_Implicit((Object)(object)Player.m_localPlayer))
			{
				return "";
			}
			return Player.m_localPlayer.GetPlayerName();
		}

		public static IEnumerable<CodeInstruction> NullSafeLocalPlayer(IEnumerable<CodeInstruction> instructions, MethodBase original, int expected)
		{
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Expected O, but got Unknown
			FieldInfo fieldInfo = AccessTools.Field(typeof(Player), "m_localPlayer");
			List<CodeInstruction> list = instructions.ToList();
			int num = 0;
			for (int i = 0; i < list.Count - 1; i++)
			{
				if (CodeInstructionExtensions.LoadsField(list[i], fieldInfo, false) && !(list[i + 1].opcode != OpCodes.Callvirt) && list[i + 1].operand is MethodInfo methodInfo && methodInfo.DeclaringType.IsAssignableFrom(typeof(Player)) && Replacements.TryGetValue(methodInfo.Name, out var value))
				{
					CodeInstruction val = new CodeInstruction(OpCodes.Call, (object)value);
					val.labels.AddRange(list[i].labels);
					val.labels.AddRange(list[i + 1].labels);
					val.blocks.AddRange(list[i].blocks);
					val.blocks.AddRange(list[i + 1].blocks);
					list[i] = val;
					list.RemoveAt(i + 1);
					num++;
				}
			}
			if (num != expected)
			{
				Plugin.logger.LogWarning((object)$"{original.DeclaringType.Name}.{original.Name}: replaced {num} Player.m_localPlayer call(s), expected {expected}. The game changed this method; the patch needs reviewing.");
			}
			return list;
		}
	}
	public class Utilities
	{
		public static bool IsDebugBuild()
		{
			return false;
		}
	}
	public static class VanillaDrift
	{
		public const string ReviewedForGameVersion = "1.0.15";

		private static readonly Dictionary<MethodBase, string[]> Expected = new Dictionary<MethodBase, string[]>
		{
			{
				AccessTools.Method(typeof(ZoneSystem), "Update", (Type[])null, (Type[])null),
				new string[15]
				{
					"CinematicsManager::IsPlaying", "TextViewer::IsShowingIntro", "Utils::GetTimeBudgetForTargetFrameRate", "ZNet::GetConnectionStatus", "ZNet::GetPeers", "ZNet::GetReferencePosition", "ZNet::IsServer", "ZNet::get_instance", "ZNetPeer::GetRefPos", "ZoneSystem::CreateGhostZones",
					"ZoneSystem::CreateLocalZones", "ZoneSystem::TimeSinceStart", "ZoneSystem::UpdatePrefabLifetimes", "ZoneSystem::UpdateTTL", "ZoneSystem::get_LocationsGenerated"
				}
			},
			{
				AccessTools.Method(typeof(ZoneSystem), "IsActiveAreaLoaded", (Type[])null, (Type[])null),
				new string[7] { "SimulationDistance::get_IsClassic", "SimulationDistance::get_NearSimulationDistance", "Vector2s::.ctor", "ZNet::GetReferencePosition", "ZNet::get_instance", "ZoneSystem::GetZone", "ZoneSystem::ZonesWithinRadius" }
			},
			{
				AccessTools.Method(typeof(ZNetScene), "CreateDestroyObjects", (Type[])null, (Type[])null),
				new string[8] { "ZDOMan::FindSectorObjects", "ZDOMan::get_instance", "ZNet::GetReferencePosition", "ZNet::GetSyncedSimulationDistance", "ZNet::get_instance", "ZNetScene::CreateObjects", "ZNetScene::RemoveObjects", "ZoneSystem::GetZone" }
			},
			{
				AccessTools.Method(typeof(ZDOMan), "ReleaseNearbyZDOS", (Type[])null, (Type[])null),
				new string[14]
				{
					"SimulationDistance::.ctor", "SimulationDistance::get_IsClassic", "SimulationDistance::get_NearSimulationDistance", "ZDO::GetOwner", "ZDO::GetPosition", "ZDO::HasOwner", "ZDO::SetOwner", "ZDO::get_Persistent", "ZDOMan::FindSectorObjects", "ZDOMan::IsInPeerActiveArea",
					"ZNet::GetSyncedSimulationDistance", "ZNet::get_instance", "ZNetScene::InActiveArea", "ZoneSystem::GetZone"
				}
			},
			{
				AccessTools.Method(typeof(ZDOMan), "SendZDOToPeers2", (Type[])null, (Type[])null),
				new string[1] { "ZDOMan::SendZDOs" }
			},
			{
				AccessTools.Method(typeof(ZNetScene), "OutsideActiveArea", new Type[1] { typeof(Vector3) }, (Type[])null),
				new string[4] { "ZNet::GetReferencePosition", "ZNet::get_instance", "ZNetScene::OutsideActiveArea", "ZoneSystem::GetZone" }
			},
			{
				AccessTools.Method(typeof(Ship), "UpdateOwner", (Type[])null, (Type[])null),
				new string[8] { "Ship::GetNewOwnerID", "Ship::IsPlayerInBoat", "Ship::RefreshPlayerList", "ZDO::SetOwner", "ZLog::Log", "ZNetView::GetZDO", "ZNetView::IsOwner", "ZNetView::IsValid" }
			}
		};

		public static void Check(ManualLogSource logger)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			string text = ((object)Version.CurrentVersion/*cast due to .constrained prefix*/).ToString();
			int num = 0;
			foreach (KeyValuePair<MethodBase, string[]> item in Expected)
			{
				if (item.Key == null)
				{
					logger.LogError((object)"Vanilla drift check: a replaced method no longer exists in this game version.");
					num++;
					continue;
				}
				HashSet<string> hashSet = GameCalls(item.Key);
				List<string> list = (from s in hashSet.Except(item.Value)
					orderby s
					select s).ToList();
				List<string> list2 = (from s in item.Value.Except(hashSet)
					orderby s
					select s).ToList();
				if (list.Count != 0 || list2.Count != 0)
				{
					num++;
					logger.LogWarning((object)("Vanilla " + item.Key.DeclaringType.Name + "." + item.Key.Name + " changed since this mod's replacement was reviewed (game 1.0.15, running " + text + "). Now calls: [" + string.Join(", ", list) + "]. No longer calls: [" + string.Join(", ", list2) + "]. The replacement may be dropping new vanilla behaviour."));
				}
			}
			if (num == 0)
			{
				logger.LogInfo((object)string.Format("Vanilla drift check passed ({0} replaced methods, reviewed for {1}, running {2}).", Expected.Count, "1.0.15", text));
			}
		}

		private static HashSet<string> GameCalls(MethodBase method)
		{
			HashSet<string> hashSet = new HashSet<string>();
			foreach (CodeInstruction originalInstruction in PatchProcessor.GetOriginalInstructions(method, (ILGenerator)null))
			{
				if ((originalInstruction.opcode == OpCodes.Call || originalInstruction.opcode == OpCodes.Callvirt || originalInstruction.opcode == OpCodes.Newobj) && originalInstruction.operand is MethodBase methodBase && methodBase.DeclaringType != null)
				{
					string name = methodBase.DeclaringType.Assembly.GetName().Name;
					if (name == "assembly_valheim" || name == "assembly_utils")
					{
						hashSet.Add(methodBase.DeclaringType.Name + "::" + methodBase.Name);
					}
				}
			}
			return hashSet;
		}
	}
}
namespace Serverbound.Requirements
{
	public class PatchRequirement
	{
		public class DebugBuild : IPatchRequirement
		{
			public const string name = "DebugBuild";

			string IPatchRequirement.Name => "DebugBuild";

			Func<bool> IPatchRequirement.Checker => Utilities.IsDebugBuild;
		}
	}
}
namespace Serverbound.Patching
{
	public interface IPatchRequirement
	{
		string Name { get; }

		Func<bool> Checker { get; }
	}
	public class PatchRequirements
	{
		private readonly Dictionary<string, Func<bool>> _requirements;

		public PatchRequirements()
		{
			_requirements = new Dictionary<string, Func<bool>>();
		}

		public PatchRequirements(Dictionary<string, Func<bool>> requirements)
		{
			_requirements = requirements;
		}

		public PatchRequirements AddRequirement(IPatchRequirement patchRequirement)
		{
			_requirements.Add(patchRequirement.Name, patchRequirement.Checker);
			return this;
		}

		public bool IsAllowed(string requirement_name)
		{
			_requirements.TryGetValue(requirement_name, out var value);
			return value?.Invoke() ?? false;
		}
	}
	[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
	public class PatchRequiresAttribute : Attribute
	{
		public readonly string requirement_name;

		public PatchRequiresAttribute(string requirement_name)
		{
			this.requirement_name = requirement_name;
		}
	}
	public class HarmonyFeaturesPatcher
	{
		private readonly PatchRequirements _patchRequirements;

		public HarmonyFeaturesPatcher(PatchRequirements availableFeatures)
		{
			_patchRequirements = availableFeatures;
		}

		public void PatchAll(Type[] types, Harmony harmony_instance)
		{
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			foreach (Type type in types)
			{
				if (!type.GetCustomAttributes<PatchRequiresAttribute>().ToList().Any((PatchRequiresAttribute attribute) => !_patchRequirements.IsAllowed(attribute.requirement_name)))
				{
					ZLog.Log((object)("Patching: " + type.ToString()));
					new PatchClassProcessor(harmony_instance, type).Patch();
				}
				else
				{
					ZLog.Log((object)("Patch disabled: " + type.ToString()));
				}
			}
		}
	}
}
namespace Serverbound.Features
{
	public class Compat_ValheimCommunityPatch : IFeature
	{
		[HarmonyPatch(typeof(ZNetScene), "Awake")]
		public static class ZNetScene_Awake_Patch
		{
			internal static bool s_done;

			private static void Postfix()
			{
				if (s_done || !SimulationPatch.Active || SimulationPatch.VcpAssembly == null)
				{
					return;
				}
				s_done = true;
				removed.Clear();
				try
				{
					TakeOver("CreateObjectsSorted", Configuration.compatVcpSpawnQueue, "spawn queue", "new objects are created in order of distance from the world origin, not from the nearest player.");
					TakeOver("RemoveObjects", Configuration.compatVcpUnload, "zone-diff unload", "objects around players far from the world origin can be destroyed and created again on every pass.");
					foreach (var item in removed)
					{
						SimulationPatch.RememberRemoved(item);
					}
				}
				catch (Exception ex)
				{
					foreach (var item2 in removed)
					{
						try
						{
							SimulationPatch.Restore(item2);
						}
						catch (Exception ex2)
						{
							Plugin.logger.LogError((object)("VCP takeover rollback failed; stop the server. " + ex2));
						}
					}
					Plugin.logger.LogError((object)("VCP takeover rejected. " + ex));
					Plugin.DisableSimulation();
				}
				finally
				{
					removed.Clear();
				}
			}
		}

		public const string PluginId = "MidnightsFX.ValheimCommunityPatch";

		public static bool UnloadKept;

		private static readonly List<(MethodBase Target, Patch Hook, HarmonyPatchType Kind)> removed = new List<(MethodBase, Patch, HarmonyPatchType)>();

		public bool FeatureEnabled()
		{
			return true;
		}

		private static void TakeOver(string method, ConfigEntry<bool> keep, string what, string cost)
		{
			MethodBase methodBase = AccessTools.DeclaredMethod(typeof(ZNetScene), method, (Type[])null, (Type[])null);
			if (keep.Value)
			{
				if (method == "RemoveObjects")
				{
					UnloadKept = true;
					Plugin.logger.LogInfo((object)("Keeping ValheimCommunityPatch's " + what + " on ZNetScene." + method + " ([Compat] " + ((ConfigEntryBase)keep).Definition.Key + " = true); the object lists are marked as edited on every pass so it uses the game's own unload check."));
				}
				else
				{
					Plugin.logger.LogWarning((object)("Keeping ValheimCommunityPatch's " + what + " on ZNetScene." + method + " ([Compat] " + ((ConfigEntryBase)keep).Definition.Key + " = true): " + cost));
				}
				return;
			}
			string text = ((method == "RemoveObjects") ? "ZoneDiffRemovalPatch" : "SpawnQueueCachePatch");
			string text2 = ((method == "RemoveObjects") ? "RemoveObjectsPrefix" : "CreateObjectsSortedPrefix");
			Assembly vcpAssembly = SimulationPatch.VcpAssembly;
			Guard.Build(vcpAssembly, "E48804F2280878B1BB30393C57C354EFDAA68F106A0B2C77F150C113A25EAA68");
			MethodInfo hook = AccessTools.DeclaredMethod(Guard.Type(vcpAssembly, "ValheimCommunityPatch.Patches.Performance." + text), text2, (Type[])null, (Type[])null);
			(MethodBase, Patch, HarmonyPatchType) item = SimulationPatch.ExactHook(typeof(ZNetScene), method, hook, "MidnightsFX.ValheimCommunityPatch", (HarmonyPatchType)1);
			removed.Add(item);
			Plugin.harmony.Unpatch(methodBase, hook);
			if (SimulationPatch.All(methodBase).Any((Patch patch) => patch.PatchMethod == hook))
			{
				throw new InvalidOperationException("Could not remove ValheimCommunityPatch's " + what + " from ZNetScene." + method + ": " + cost);
			}
			Plugin.logger.LogInfo((object)("Removed ValheimCommunityPatch's " + what + " from ZNetScene." + method + ": it works around the server's reference position, the world origin on a dedicated server, not around the players. Set [Compat] " + ((ConfigEntryBase)keep).Definition.Key + " = true to keep it."));
		}
	}
	public class Core : IFeature
	{
		[HarmonyPatch(typeof(Pickable), "RPC_Pick")]
		public static class Pickable_RPC_Pick_Patch
		{
			private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod)
			{
				return ServerSafe.NullSafeLocalPlayer(instructions, __originalMethod, 1);
			}
		}

		[HarmonyPatch(typeof(Ship), "UpdateSailSize")]
		public static class Ship_UpdateSailSize_Patch
		{
			private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod)
			{
				return ServerSafe.NullSafeLocalPlayer(instructions, __originalMethod, 2);
			}
		}

		[HarmonyPatch(typeof(CookingStation), "SpawnItem")]
		public static class CookingStation_SpawnItem_Patch
		{
			private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod)
			{
				return ServerSafe.NullSafeLocalPlayer(instructions, __originalMethod, 2);
			}
		}

		[HarmonyPatch(typeof(Leviathan), "RPC_Left")]
		public static class Leviathan_RPC_Left_Patch
		{
			private static bool Prefix()
			{
				return (Object)(object)Player.m_localPlayer != (Object)null;
			}
		}

		[HarmonyPatch(typeof(ZNetScene), "CreateDestroyObjects")]
		public class CreateDestroyObjects_Patch
		{
			private static readonly HashSet<ZDO> s_seen = new HashSet<ZDO>();

			private static bool Prefix(ZNetScene __instance)
			{
				//IL_001f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0024: Unknown result type (might be due to invalid IL or missing references)
				//IL_004b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0050: 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_005c: Unknown result type (might be due to invalid IL or missing references)
				//IL_005e: Unknown result type (might be due to invalid IL or missing references)
				List<ZDO> tempCurrentObjects = __instance.m_tempCurrentObjects;
				List<ZDO> tempCurrentDistantObjects = __instance.m_tempCurrentDistantObjects;
				tempCurrentObjects.Clear();
				tempCurrentDistantObjects.Clear();
				SimulationDistance syncedSimulationDistance = ZNet.instance.GetSyncedSimulationDistance();
				foreach (ZNetPeer peer in ZNet.instance.GetPeers())
				{
					if (peer.IsReady())
					{
						Vector2s zone = ZoneSystem.GetZone(peer.GetRefPos());
						ZDOMan.instance.FindSectorObjects(zone, syncedSimulationDistance, tempCurrentObjects, tempCurrentDistantObjects);
					}
				}
				RemoveDuplicates(tempCurrentObjects);
				RemoveDuplicates(tempCurrentDistantObjects);
				if (Compat_ValheimCommunityPatch.UnloadKept)
				{
					tempCurrentObjects.Reverse(0, 0);
					tempCurrentDistantObjects.Reverse(0, 0);
				}
				__instance.CreateObjects(tempCurrentObjects, tempCurrentDistantObjects);
				__instance.RemoveObjects(tempCurrentObjects, tempCurrentDistantObjects);
				return false;
			}

			private static void RemoveDuplicates(List<ZDO> zdos)
			{
				s_seen.Clear();
				zdos.RemoveAll((ZDO zdo) => !s_seen.Add(zdo));
			}
		}

		[HarmonyPatch(typeof(ZNetScene), "CreateObjectsSorted")]
		public static class ZNetScene_CreateObjectsSorted_Patch
		{
			private static readonly List<Vector3> s_peerPositions = new List<Vector3>();

			private static int s_peerPositionsFrame = -1;

			private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod)
			{
				MethodInfo methodInfo = AccessTools.Method(typeof(Utils), "DistanceSqr", new Type[2]
				{
					typeof(Vector3),
					typeof(Vector3)
				}, (Type[])null);
				MethodInfo operand = AccessTools.Method(typeof(ZNetScene_CreateObjectsSorted_Patch), "NearestPeerDistanceSqr", (Type[])null, (Type[])null);
				List<CodeInstruction> list = new List<CodeInstruction>(instructions);
				int num = 0;
				foreach (CodeInstruction item in list)
				{
					if (CodeInstructionExtensions.Calls(item, methodInfo))
					{
						item.operand = operand;
						num++;
					}
				}
				if (num != 1)
				{
					Plugin.logger.LogWarning((object)$"{__originalMethod.DeclaringType.Name}.{__originalMethod.Name}: replaced {num} sort distance call(s), expected 1. The game changed this method; the patch needs reviewing.");
				}
				return list;
			}

			public static float NearestPeerDistanceSqr(Vector3 referencePosition, Vector3 position)
			{
				//IL_0077: Unknown result type (might be due to invalid IL or missing references)
				//IL_0078: Unknown result type (might be due to invalid IL or missing references)
				//IL_0094: Unknown result type (might be due to invalid IL or missing references)
				//IL_0099: Unknown result type (might be due to invalid IL or missing references)
				//IL_009c: Unknown result type (might be due to invalid IL or missing references)
				//IL_009e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0048: Unknown result type (might be due to invalid IL or missing references)
				if (s_peerPositionsFrame != Time.frameCount)
				{
					s_peerPositionsFrame = Time.frameCount;
					s_peerPositions.Clear();
					foreach (ZNetPeer peer in ZNet.instance.GetPeers())
					{
						if (peer.IsReady())
						{
							s_peerPositions.Add(peer.GetRefPos());
						}
					}
				}
				if (s_peerPositions.Count == 0)
				{
					return Utils.DistanceSqr(referencePosition, position);
				}
				float num = float.MaxValue;
				foreach (Vector3 s_peerPosition in s_peerPositions)
				{
					num = Mathf.Min(num, Utils.DistanceSqr(s_peerPosition, position));
				}
				return num;
			}
		}

		[HarmonyPatch(typeof(ZoneSystem), "IsActiveAreaLoaded")]
		public static class ZoneSystem_IsActiveAreaLoaded_Patch
		{
			private static bool Prefix(ZoneSystem __instance, ref bool __result)
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				//IL_0038: Unknown result type (might be due to invalid IL or missing references)
				//IL_003d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0042: Unknown result type (might be due to invalid IL or missing references)
				//IL_0044: Unknown result type (might be due to invalid IL or missing references)
				//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
				//IL_0051: Unknown result type (might be due to invalid IL or missing references)
				//IL_009f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0086: Unknown result type (might be due to invalid IL or missing references)
				//IL_0073: Unknown result type (might be due to invalid IL or missing references)
				//IL_0075: Unknown result type (might be due to invalid IL or missing references)
				SimulationDistance simulationDistance = __instance.m_simulationDistance;
				int nearSimulationDistance = ((SimulationDistance)(ref simulationDistance)).NearSimulationDistance;
				Vector2s val = default(Vector2s);
				foreach (ZNetPeer peer in ZNet.instance.GetPeers())
				{
					if (!peer.IsReady())
					{
						continue;
					}
					Vector2s zone = ZoneSystem.GetZone(peer.GetRefPos());
					for (int i = zone.y - nearSimulationDistance; i <= zone.y + nearSimulationDistance; i++)
					{
						for (int j = zone.x - nearSimulationDistance; j <= zone.x + nearSimulationDistance; j++)
						{
							((Vector2s)(ref val))..ctor(j, i);
							if ((((SimulationDistance)(ref simulationDistance)).IsClassic || __instance.ZonesWithinRadius(zone, val, nearSimulationDistance, false)) && !__instance.m_zones.ContainsKey(val))
							{
								__result = false;
								return false;
							}
						}
					}
				}
				__result = true;
				return false;
			}
		}

		[HarmonyPatch(typeof(ZoneSystem), "Update")]
		public static class ZoneSystem_Update_Patch
		{
			private static int s_nextPeer;

			private static bool Prefix(ZoneSystem __instance)
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				//IL_0006: Invalid comparison between Unknown and I4
				if ((int)ZNet.GetConnectionStatus() != 2 || (ZNet.instance.IsServer() && !__instance.LocationsGenerated))
				{
					return true;
				}
				__instance.m_lastFixedTime = Time.fixedTime;
				__instance.m_updateTimer += Time.deltaTime;
				if (__instance.m_updateTimer > 0.1f)
				{
					__instance.m_updateTimer = 0f;
					__instance.UpdateTTL(0.1f);
					if (ZNet.instance.IsServer())
					{
						long timestamp = Stopwatch.GetTimestamp();
						List<ZNetPeer> peers = ZNet.instance.GetPeers();
						int num = SpawnZones(__instance, peers, ghost: false);
						int ghost = ((num == 0) ? SpawnZones(__instance, peers, ghost: true) : 0);
						PerformanceStats.Zones(num, ghost, timestamp);
					}
					__instance.UpdatePrefabLifetimes();
				}
				return false;
			}

			private static int SpawnZones(ZoneSystem zoneSystem, List<ZNetPeer> peers, bool ghost)
			{
				//IL_004e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0053: Unknown result type (might be due to invalid IL or missing references)
				//IL_0063: Unknown result type (might be due to invalid IL or missing references)
				//IL_0059: Unknown result type (might be due to invalid IL or missing references)
				int num = ((Configuration.maxZonesPerTick.Value > 0) ? Configuration.maxZonesPerTick.Value : int.MaxValue);
				int count = peers.Count;
				int num2 = 0;
				for (int i = 0; i < count; i++)
				{
					if (num2 >= num)
					{
						break;
					}
					int num3 = (s_nextPeer + i) % count;
					if (peers[num3].IsReady())
					{
						Vector3 refPos = peers[num3].GetRefPos();
						if (ghost ? zoneSystem.CreateGhostZones(refPos) : zoneSystem.CreateLocalZones(refPos))
						{
							num2++;
							s_nextPeer = (num3 + 1) % count;
						}
					}
				}
				return num2;
			}
		}

		[HarmonyPatch(typeof(ZDOMan), "ReleaseNearbyZDOS")]
		public static class ZDOMan_ReleaseNearbyZDOS_Patch
		{
			private static bool Prefix(ZDOMan __instance, Vector3 refPosition, long uid)
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				//IL_0006: 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_001e: 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_0030: Unknown result type (might be due to invalid IL or missing references)
				//IL_0066: Unknown result type (might be due to invalid IL or missing references)
				//IL_006b: 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_0099: Unknown result type (might be due to invalid IL or missing references)
				//IL_009e: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
				Vector2s zone = ZoneSystem.GetZone(refPosition);
				List<ZDO> tempNearObjects = __instance.m_tempNearObjects;
				tempNearObjects.Clear();
				SimulationDistance syncedSimulationDistance = ZNet.instance.GetSyncedSimulationDistance();
				__instance.FindSectorObjects(zone, new SimulationDistance(((SimulationDistance)(ref syncedSimulationDistance)).NearSimulationDistance, 0, ((SimulationDistance)(ref syncedSimulationDistance)).IsClassic), tempNearObjects, (List<ZDO>)null);
				long uID = ZNet.GetUID();
				foreach (ZDO item in tempNearObjects)
				{
					if (!item.Persistent)
					{
						continue;
					}
					Vector3 position = item.GetPosition();
					bool flag = false;
					foreach (ZNetPeer peer in ZNet.instance.GetPeers())
					{
						if (peer.IsReady() && ZNetScene.InActiveArea(position, ZoneSystem.GetZone(peer.GetRefPos())))
						{
							flag = true;
							break;
						}
					}
					long owner = item.GetOwner();
					if (owner == uid || owner == uID)
					{
						if (!flag)
						{
							item.SetOwner(0L);
						}
					}
					else if ((owner == 0L || !__instance.IsInPeerActiveArea(position, owner)) && flag)
					{
						item.SetOwner(uID);
					}
				}
				return false;
			}
		}

		[HarmonyPatch(typeof(RandEventSystem), "FixedUpdate")]
		public static class RandEventSystem_FixedUpdate_Patch
		{
			private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod)
			{
				MethodInfo isAnyPlayerInEventArea = AccessTools.Method(typeof(RandEventSystem), "IsAnyPlayerInEventArea", (Type[])null, (Type[])null);
				MethodInfo isInsideRandomEventArea = AccessTools.Method(typeof(RandEventSystem), "IsInsideRandomEventArea", (Type[])null, (Type[])null);
				MethodInfo methodInfo = AccessTools.Method(typeof(Object), "op_Implicit", (Type[])null, (Type[])null);
				MethodInfo methodInfo2 = AccessTools.PropertyGetter(typeof(Component), "transform");
				MethodInfo methodInfo3 = AccessTools.PropertyGetter(typeof(Transform), "position");
				FieldInfo fieldInfo = AccessTools.Field(typeof(Player), "m_localPlayer");
				FieldInfo fieldInfo2 = AccessTools.Field(typeof(RandEventSystem), "m_randomEvent");
				List<CodeInstruction> list = instructions.ToList();
				int num = list.FindIndex((CodeInstruction code) => CodeInstructionExtensions.Calls(code, isAnyPlayerInEventArea));
				CodeInstruction val = ((num >= 0 && num + 1 < list.Count) ? LoadOf(list[num + 1]) : null);
				int num2 = -1;
				for (int num3 = Math.Max(num, 0); num3 + 2 < list.Count; num3++)
				{
					if (val == null)
					{
						break;
					}
					if (CodeInstructionExtensions.LoadsField(list[num3], fieldInfo, false) && CodeInstructionExtensions.Calls(list[num3 + 1], methodInfo) && (list[num3 + 2].opcode == OpCodes.Brfalse || list[num3 + 2].opcode == OpCodes.Brfalse_S))
					{
						num2 = num3 + 2;
						break;
					}
				}
				int num4 = ((num2 >= 0) ? list.FindIndex(num2, (CodeInstruction code) => CodeInstructionExtensions.Calls(code, isInsideRandomEventArea)) : (-1));
				int num5 = num4 - 6;
				bool flag = num5 > num2 && CodeInstructionExtensions.IsLdarg(list[num5], (int?)0) && CodeInstructionExtensions.IsLdarg(list[num5 + 1], (int?)0) && CodeInstructionExtensions.LoadsField(list[num5 + 2], fieldInfo2, false) && CodeInstructionExtensions.LoadsField(list[num5 + 3], fieldInfo, false) && CodeInstructionExtensions.Calls(list[num5 + 4], methodInfo2) && CodeInstructionExtensions.Calls(list[num5 + 5], methodInfo3);
				if (val == null || num2 < 0 || !flag)
				{
					string text = ((val == null) ? "the IsAnyPlayerInEventArea result" : ((num2 < 0) ? "the local player check" : "the IsInsideRandomEventArea call for the local player"));
					Plugin.logger.LogWarning((object)(__originalMethod.DeclaringType.Name + "." + __originalMethod.Name + ": " + text + " not found; left unchanged, so random events will not become active on the server. The game changed this method; the patch needs reviewing."));
					return list;
				}
				list[num2].opcode = ((list[num2].opcode == OpCodes.Brfalse_S) ? OpCodes.Brtrue_S : OpCodes.Brtrue);
				for (int num6 = num5; num6 <= num4; num6++)
				{
					val.labels.AddRange(list[num6].labels);
					val.blocks.AddRange(list[num6].blocks);
				}
				list.RemoveRange(num5, num4 - num5 + 1);
				list.Insert(num5, val);
				return list;
			}

			private static CodeInstruction LoadOf(CodeInstruction store)
			{
				//IL_0018: Unknown result type (might be due to invalid IL or missing references)
				//IL_001e: Expected O, but got Unknown
				//IL_0036: Unknown result type (might be due to invalid IL or missing references)
				//IL_003c: Expected O, but got Unknown
				//IL_0054: Unknown result type (might be due to invalid IL or missing references)
				//IL_005a: Expected O, but got Unknown
				//IL_0072: Unknown result type (might be due to invalid IL or missing references)
				//IL_0078: Expected O, but got Unknown
				//IL_0095: Unknown result type (might be due to invalid IL or missing references)
				//IL_009b: Expected O, but got Unknown
				//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
				//IL_00be: Expected O, but got Unknown
				if (store.opcode == OpCodes.Stloc_0)
				{
					return new CodeInstruction(OpCodes.Ldloc_0, (object)null);
				}
				if (store.opcode == OpCodes.Stloc_1)
				{
					return new CodeInstruction(OpCodes.Ldloc_1, (object)null);
				}
				if (store.opcode == OpCodes.Stloc_2)
				{
					return new CodeInstruction(OpCodes.Ldloc_2, (object)null);
				}
				if (store.opcode == OpCodes.Stloc_3)
				{
					return new CodeInstruction(OpCodes.Ldloc_3, (object)null);
				}
				if (store.opcode == OpCodes.Stloc_S)
				{
					return new CodeInstruction(OpCodes.Ldloc_S, store.operand);
				}
				if (store.opcode == OpCodes.Stloc)
				{
					return new CodeInstruction(OpCodes.Ldloc, store.operand);
				}
				return null;
			}
		}

		[HarmonyPatch(typeof(SpawnSystem), "UpdateSpawning")]
		public static class SpawnSystem_UpdateSpawning_Patch
		{
			private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod)
			{
				//IL_0009: 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_003c: Expected O, but got Unknown
				//IL_004a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0050: Expected O, but got Unknown
				//IL_0073: Unknown result type (might be due to invalid IL or missing references)
				//IL_0079: Expected O, but got Unknown
				//IL_009b: Unknown result type (might be due to invalid IL or missing references)
				//IL_00a1: Expected O, but got Unknown
				//IL_01b7: Unknown result type (might be due to invalid IL or missing references)
				//IL_01bd: Expected O, but got Unknown
				//IL_010a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0110: Expected O, but got Unknown
				List<CodeInstruction> list = instructions.ToList();
				CodeMatcher val = new CodeMatcher((IEnumerable<CodeInstruction>)list, (ILGenerator)null).MatchForward(true, (CodeMatch[])(object)new CodeMatch[4]
				{
					new CodeMatch((OpCode?)OpCodes.Ldsfld, (object)AccessTools.Field(typeof(Player), "m_localPlayer"), (string)null),
					new CodeMatch((OpCode?)OpCodes.Ldnull, (object)null, (string)null),
					new CodeMatch((OpCode?)OpCodes.Call, (object)AccessTools.Method(typeof(Object), "op_Equality", (Type[])null, (Type[])null), (string)null),
					new CodeMatch((Func<CodeInstruction, bool>)((CodeInstruction instruction) => instruction.opcode == OpCodes.Brfalse || instruction.opcode == OpCodes.Brfalse_S), (string)null)
				});
				bool isValid = val.IsValid;
				if (isValid)
				{
					val.Opcode = ((val.Opcode == OpCodes.Brfalse_S) ? OpCodes.Brtrue_S : OpCodes.Brtrue);
					val.MatchForward(false, (CodeMatch[])(object)new CodeMatch[1]
					{
						new CodeMatch((OpCode?)OpCodes.Callvirt, (object)AccessTools.Method(typeof(RandEventSystem), "GetCurrentSpawners", (Type[])null, (Type[])null), (string)null)
					});
				}
				if (!isValid || !val.IsValid)
				{
					string text = ((!isValid) ? "the local player check" : "the RandEventSystem.GetCurrentSpawners call");
					Plugin.logger.LogWarning((object)(__originalMethod.DeclaringType.Name + "." + __originalMethod.Name + ": " + text + " not found; left unchanged, so the server will not spawn creatures. The game changed this method; the patch needs reviewing."));
					return list;
				}
				val.Opcode = OpCodes.Call;
				val.Operand = AccessTools.Method(typeof(Core), "GetCurrentSpawners", (Type[])null, (Type[])null);
				val.Insert((CodeInstruction[])(object)new CodeInstruction[1]
				{
					new CodeInstruction(OpCodes.Ldarg_0, (object)null)
				});
				return val.InstructionEnumeration();
			}
		}

		[HarmonyPatch(typeof(ZNetScene), "OutsideActiveArea", new Type[] { typeof(Vector3) })]
		public static class ZNetScene_OutsideActiveArea_Patch
		{
			private static bool Prefix(ref bool __result, ZNetScene __instance, Vector3 point)
			{
				//IL_0025: Unknown result type (might be due to invalid IL or missing references)
				//IL_0027: Unknown result type (might be due to invalid IL or missing references)
				//IL_002c: Unknown result type (might be due to invalid IL or missing references)
				__result = true;
				foreach (ZNetPeer peer in ZNet.instance.GetPeers())
				{
					if (peer.IsReady() && !ZNetScene.OutsideActiveArea(point, ZoneSystem.GetZone(peer.GetRefPos())))
					{
						__result = false;
					}
				}
				return false;
			}
		}

		[HarmonyPatch(typeof(ZRoutedRpc), "RouteRPC")]
		public static class ZRoutedRpc_RouteRPC_Patch
		{
			private static void Prefix(RoutedRPCData rpcData)
			{
				//IL_0024: Unknown result type (might be due to invalid IL or missing references)
				if (rpcData.m_methodHash == StringExtensionMethods.GetStableHashCode("RequestRespons"))
				{
					bool flag = rpcData.m_parameters.ReadBool();
					ZDO zDO = ZDOMan.instance.GetZDO(rpcData.m_targetZDO);
					if (zDO != null && flag)
					{
						Plugin.logger.LogDebug((object)$"RequestRespons: Setting ship's owner to {rpcData.m_targetPeerID}");
						zDO.SetOwner(rpcData.m_targetPeerID);
					}
				}
			}
		}

		[HarmonyPatch(typeof(Humanoid), "UpdateAttack")]
		public static class Humanoid_UpdateAttack_Patch
		{
			private static void Prefix(ref Humanoid __instance)
			{
				if (__instance.m_currentAttack != null && (Object)(object)__instance.m_currentAttack.m_character == (Object)null)
				{
					__instance.m_currentAttack = null;
				}
			}
		}

		[HarmonyPatch(typeof(Ship), "UpdateOwner")]
		public static class Ship_UpdateOwner_Patch
		{
			private static bool Prefix(ref Ship __instance)
			{
				if (!__instance.m_nview.IsValid())
				{
					return false;
				}
				ZDO zDO = __instance.m_nview.GetZDO();
				if (zDO.GetInt("InUse", 0) == 0)
				{
					if (!__instance.m_shipControlls.HaveValidUser())
					{
						__instance.m_lastWaterImpactTime = Time.time;
						zDO.SetOwner(ZNet.GetUID());
						return false;
					}
					long user = __instance.m_shipControlls.GetUser();
					Player val = ((user != 0L) ? Player.GetPlayer(user) : null);
					if ((Object)(object)val != (Object)null)
					{
						long owner = ((Character)val).GetOwner();
						Plugin.logger.LogDebug((object)$"UpdateOwner: Setting ship's owner to {owner}");
						zDO.SetOwner(owner);
					}
				}
				return false;
			}
		}

		[HarmonyPatch(typeof(AudioMan), "Update")]
		public static class AudioMan_Update_Patch
		{
			private static bool Prefix()
			{
				return false;
			}
		}

		[HarmonyPatch(typeof(ShieldDomeImageEffect), "GetDomeColor")]
		public static class ShieldDomeImageEffect_GetDomeColor_Patch
		{
			private static bool Prefix(ref Color __result)
			{
				//IL_0010: Unknown result type (might be due to invalid IL or missing references)
				//IL_0015: Unknown result type (might be due to invalid IL or missing references)
				__result = new Color(1f, 1f, 1f);
				return false;
			}
		}

		public bool FeatureEnabled()
		{
			return true;
		}

		public static bool IsServer()
		{
			if (Object.op_Implicit((Object)(object)ZNet.instance))
			{
				return ZNet.instance.IsServer();
			}
			return false;
		}

		public static void PrintLog(string text)
		{
			Trace.WriteLine(text);
		}

		public static void PrintLog(object[] obj)
		{
			Trace.WriteLine(string.Concat(obj));
		}

		public static List<SpawnData> GetCurrentSpawners(RandEventSystem instance, SpawnSystem spawnSystem)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: 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_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			RandomEvent activeEvent = instance.m_activeEvent;
			if (activeEvent == null)
			{
				return null;
			}
			Vector3 position = spawnSystem.m_nview.GetZDO().GetPosition();
			foreach (Player allPlayer in Player.GetAllPlayers())
			{
				if (ZNetScene.InActiveArea(position, ZoneSystem.GetZone(((Component)allPlayer).transform.position)) && instance.IsInsideRandomEventArea(activeEvent, ((Component)allPlayer).transform.position))
				{
					return instance.GetCurrentSpawners();
				}
			}
			return null;
		}
	}
	[Harmony]
	internal class Debugging : IFeature
	{
		[PatchRequires("DebugBuild")]
		[HarmonyPatch(typeof(Chat), "RPC_ChatMessage")]
		public static class Chat_RPC_ChatMessage_Patch
		{
			private static void Prefix(ref long sender, ref string text)
			{
				//IL_002a: Unknown result type (might be due to invalid IL or missing references)
				ZNetPeer peer = ZNet.instance.GetPeer(sender);
				if (peer == null)
				{
					return;
				}
				if (text == "startevent")
				{
					RandEventSystem.instance.SetRandomEventByName("army_theelder", peer.GetRefPos());
				}
				else if (text == "stopevent")
				{
					RandEventSystem.instance.ResetRandomEvent();
				}
				else if (text.StartsWith("maxobjects"))
				{
					string[] array = text.Split(new char[1] { ' ' });
					if (array.Length > 1 && int.TryParse(array[1], out var result))
					{
						Configuration.maxObjectsPerFrame.Value = result;
					}
				}
			}
		}

		public bool FeatureEnabled()
		{
			return Utilities.IsDebugBuild();
		}
	}
	public class DungeonLoadGuard : IFeature
	{
		[HarmonyPatch(typeof(BundleLoader), "OnLoadAsyncCompleted", new Type[] { })]
		public static class BundleLoader_OnLoadAsyncCompleted_Patch
		{
			private static bool Prefix(ref BundleLoader __instance)
			{
				AssetBundleCreateRequestWrapper asyncLoadRequest = __instance.m_asyncLoadRequest;
				if (asyncLoadRequest == null || (Object)(object)asyncLoadRequest.AssetBundle != (Object)null)
				{
					return true;
				}
				AssetBundle val = (__instance.m_bundle = FindLoaded(__instance.m_bundleName, __instance.m_bundlePath));
				__instance.m_asyncLoadRequest = null;
				LogOnce("bundle " + __instance.m_bundleName, ((Object)(object)val != (Object)null) ? ("Unity refused to load bundle " + __instance.m_bundleName + " again because it is already loaded; using the one it holds, so what needs it still loads.") : ("Unity returned no bundle for " + __instance.m_bundleName + "; reporting its assets as failed rather than letting the game throw on it."));
				if (__instance.m_shouldBeLoaded)
				{
					((BundleLoader)(ref __instance)).InvokeCallbacks((LoadResult)(!((Object)(object)val != (Object)null)));
				}
				else if (BundleLoader.s_tickOnAsyncOperationComplete)
				{
					((BundleLoader)(ref __instance)).TickAsync();
				}
				return false;
			}
		}

		[HarmonyPatch(typeof(AssetLoader), "LoadAssetFromBundleAsync")]
		public static class AssetLoader_LoadAssetFromBundleAsync_Patch
		{
			private unsafe static bool Prefix(ref AssetLoader __instance)
			{
				//IL_002f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0034: Unknown result type (might be due to invalid IL or missing references)
				//IL_004d: Unknown result type (might be due to invalid IL or missing references)
				AssetBundleLoader instance = AssetBundleLoader.Instance;
				if (instance == null || (Object)(object)((BundleLoader)(ref instance.m_bundleLoaders[__instance.m_bundleLoaderIndex])).Bundle != (Object)null)
				{
					return true;
				}
				AssetID assetID = __instance.m_assetID;
				LogOnce("asset " + ((object)(*(AssetID*)(&assetID))/*cast due to .constrained prefix*/).ToString(), $"Asset {__instance.m_assetID} ({__instance.m_assetPathInBundle}) has no bundle to load from; reporting it as failed.");
				((AssetLoader)(ref __instance)).InvokeCallbacks((LoadResult)1);
				return false;
			}
		}

		[HarmonyPatch(typeof(DungeonGenerator), "OnRoomLoaded")]
		[HarmonyAfter(new string[] { "MidnightsFX.ValheimCommunityPatch" })]
		public static class DungeonGenerator_OnRoomLoaded_Patch
		{
			private static bool Prefix(DungeonGenerator __instance, LoadResult result, bool __runOriginal)
			{
				//IL_0003: Unknown result type (might be due to invalid IL or missing references)
				//IL_001f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0024: 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)
				//IL_002b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0030: Unknown result type (might be due to invalid IL or missing references)
				//IL_004e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0054: Unknown result type (might be due to invalid IL or missing references)
				if (!__runOriginal || (int)result == 0 || (Object)(object)__instance == (Object)null || !Object.op_Implicit((Object)(object)__instance))
				{
					return true;
				}
				Vector3 position = ((Component)__instance).transform.position;
				LogOnce("dungeon " + ((object)ZoneSystem.GetZone(position)/*cast due to .constrained prefix*/).ToString(), $"Dungeon {((Object)__instance).name} at {position:F0}: a room failed to load ({result}). Letting it go so its zone keeps working; it is tried again the next time it is created.");
				__instance.m_roomsToLoad = int.MaxValue;
				__instance.ReleaseHeldReferences();
				return false;
			}
		}

		private static readonly HashSet<string> s_logged = new HashSet<string>();

		public bool FeatureEnabled()
		{
			return Configuration.fixDungeonLoadGuard.Value;
		}

		private static void LogOnce(string key, string text)
		{
			if (s_logged.Add(key))
			{
				Plugin.logger.LogWarning((object)text);
			}
		}

		private static AssetBundle FindLoaded(string bundleName, string bundlePath)
		{
			string text = (string.IsNullOrEmpty(bundlePath) ? null : Path.GetFileName(bundlePath));
			foreach (AssetBundle allLoadedAssetBundle in AssetBundle.GetAllLoadedAssetBundles())
			{
				if ((Object)(object)allLoadedAssetBundle != (Object)null && (((Object)allLoadedAssetBundle).name == bundleName || (text != null && ((Object)allLoadedAssetBundle).name == text)))
				{
					return allLoadedAssetBundle;
				}
			}
			return null;
		}
	}
	public class Fixes : IFeature
	{
		[HarmonyPatch(typeof(ZDO), "Deserialize")]
		public static class ZDO_Deserialize_Patch
		{
			private static void Postfix(ZDO __instance)
			{
				if (Configuration.fixSaveClientChanges.Value && __instance.Persistent && Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.IsServer() && ZDOMan.instance != null)
				{
					ZDOMan.instance.SetDirtySector(__instance);
				}
			}
		}

		[HarmonyPatch(typeof(ZDO), "InternalSetPosition")]
		public static class ZDO_InternalSetPosition_Patch
		{
			private static void Prefix(ZDO __instance, out SectorIndex __state)
			{
				//IL_0002: Unknown result type (might be due to invalid IL or missing references)
				//IL_0007: Unknown result type (might be due to invalid IL or missing references)
				__state = FiledSector(__instance);
			}

			private static void Postfix(ZDO __instance, SectorIndex __state)
			{
				//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)
				if (Configuration.fixTeleportGhosts.Value && Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.IsServer() && ZDOMan.instance != null && !(FiledSector(__instance) == __state) && (!Object.op_Implicit((Object)(object)Game.instance) || !Game.instance.PortalPrefabHash.Contains(__instance.GetPrefab())))
				{
					ZDOMan.instance.ZDOSectorInvalidated(__instance);
				}
			}

			private static SectorIndex FiledSector(ZDO zdo)
			{
				//IL_000f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0009: Unknown result type (might be due to invalid IL or missing references)
				if (!zdo.OutsideZones)
				{
					return zdo.GetSectorIndex();
				}
				return ZoneSystem.SectorZero;
			}
		}

		public bool FeatureEnabled()
		{
			if (!Configuration.fixSaveClientChanges.Value)
			{
				return Configuration.fixTeleportGhosts.Value;
			}
			return true;
		}
	}
	public class MaxObjectsPerFrame : IFeature
	{
		[HarmonyPatch(typeof(ZNetScene), "CreateObjects")]
		public class CreateObjects_Patch
		{
			public static void ILManipulator(ILContext il)
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
				//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
				new ILCursor(il).GotoNext((MoveType)0, new Func<Instruction, bool>[5]
				{
					(Instruction i) => ILPatternMatchingExt.MatchLdarg(i, 0),
					(Instruction i) => ILPatternMatchingExt.MatchLdarg(i, 1),
					(Instruction i) => ILPatternMatchingExt.MatchLdloc(i, 0),
					(Instruction i) => ILPatternMatchingExt.MatchLdloca(i, 1),
					(Instruction i) => ILPatternMatchingExt.MatchCall<ZNetScene>(i, "CreateObjectsSorted")
				}).Emit(OpCodes.Call, (MethodBase)AccessTools.Method(typeof(MaxObjectsPerFrame), "GetMaxCreatedPerFrame", (Type[])null, (Type[])null)).Emit(OpCodes.Stloc_0);
			}
		}

		public bool FeatureEnabled()
		{
			return Configuration.maxObjectsPerFrameEnabled.Value;
		}

		public static int GetMaxCreatedPerFrame()
		{
			return Configuration.maxObjectsPerFrame.Value;
		}
	}
	public class Networking : IFeature
	{
		[HarmonyPatch(typeof(ZDOMan), "SendZDOs")]
		public static class ZDOMan_SendZDOs_Patch
		{
			private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod)
			{
				//IL_003e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0045: Expected O, but got Unknown
				MethodInfo methodInfo = AccessTools.Method(typeof(Networking), "QueueSize", (Type[])null, (Type[])null);
				List<CodeInstruction> list = new List<CodeInstruction>(instructions);
				int num = 0;
				for (int i = 0; i < list.Count; i++)
				{
					if (CodeInstructionExtensions.LoadsConstant(list[i], 10240L))
					{
						CodeInstruction val = new CodeInstruction(OpCodes.Call, (object)methodInfo);
						val.labels.AddRange(list[i].labels);
						val.blocks.AddRange(list[i].blocks);
						list[i] = val;
						num++;
					}
				}
				if (num != 2)
				{
					Plugin.logger.LogWarning((object)$"{__originalMethod.DeclaringType.Name}.{__originalMethod.Name}: replaced {num} send queue limit(s), expected 2. The game changed this method; the patch needs reviewing.");
				}
				return list;
			}

			private static void Prefix(ZDOPeer peer, bool flush)
			{
				Stats.Record(peer.m_peer, flush);
			}
		}

		[HarmonyPatch(typeof(ZSteamSocket), "RegisterGlobalCallbacks")]
		public static class ZSteamSocket_RegisterGlobalCallbacks_Patch
		{
			private static void Postfix()
			{
				int num = Configuration.networkSendRateMaxKB.Value * 1024;
				int num2 = Math.Min(Configuration.networkSendRateMinKB.Value * 1024, num);
				SetSteamConfig((ESteamNetworkingConfigValue)10, num2);
				SetSteamConfig((ESteamNetworkingConfigValue)11, num);
				Plugin.logger.LogInfo((object)$"Networking: Steam send rate {num2 / 1024}-{num / 1024} KB/s, send queue {QueueSize() / 1024} KB per player");
			}

			private static void SetSteamConfig(ESteamNetworkingConfigValue key, int value)
			{
				//IL_000d: Unknown result type (might be due to invalid IL or missing references)
				GCHandle gCHandle = GCHandle.Alloc(value, GCHandleType.Pinned);
				try
				{
					SteamGameServerNetworkingUtils.SetConfigValue(key, (ESteamNetworkingConfigScope)1, IntPtr.Zero, (ESteamNetworkingConfigDataType)1, gCHandle.AddrOfPinnedObject());
				}
				finally
				{
					gCHandle.Free();
				}
			}
		}

		private static class Stats
		{
			private class PeerStats
			{
				public string name;

				public int ticks;

				public int full;

				public int maxQueued;
			}

			private static readonly Dictionary<long, PeerStats> s_peers = new Dictionary<long, PeerStats>();

			private static float s_nextReport = -1f;

			public static void Record(ZNetPeer peer, bool flush)
			{
				int value = Configuration.networkStatsMinutes.Value;
				if (!(value <= 0 || flush) && peer != null && peer.m_socket != null)
				{
					int sendQueueSize = peer.m_socket.GetSendQueueSize();
					if (!s_peers.TryGetValue(peer.m_uid, out var value2))
					{
						value2 = new PeerStats();
						s_peers[peer.m_uid] = value2;
					}
					value2.name = peer.m_playerName;
					value2.ticks++;
					if (QueueSize() - sendQueueSize < 2048)
					{
						value2.full++;
					}
					value2.maxQueued = Math.Max(value2.maxQueued, sendQueueSize);
					float realtimeSinceStartup = Time.realtimeSinceStartup;
					if (s_nextReport < 0f)
					{
						s_nextReport = realtimeSinceStartup + (float)value * 60f;
					}
					else if (realtimeSinceStartup >= s_nextReport)
					{
						Report();
						s_nextReport = realtimeSinceStartup + (float)value * 60f;
					}
				}
			}

			private static void Report()
			{
				foreach (PeerStats value in s_peers.Values)
				{
					Plugin.logger.LogInfo((object)$"Networking {value.name}: send queue full in {100f * (float)value.full / (float)value.ticks:0.0}% of {value.ticks} send ticks, at most {value.maxQueued / 1024} of {QueueSize() / 1024} KB queued");
				}
				s_peers.Clear();
			}
		}

		private const int VanillaQueueSize = 10240;

		public bool FeatureEnabled()
		{
			return Configuration.networkingEnabled.Value;
		}

		public static int QueueSize()
		{
			return Mathf.Clamp(Configuration.networkQueueSizeKB.Value, 10, 80) * 1024;
		}
	}
	public class Performance : IFeature
	{
		[HarmonyPatch(typeof(ZDOMan), "SendZDOToPeers2")]
		public static class ZDOMan_SendZDOToPeers2_Patch
		{
			private static double s_owed;

			private static int s_next;

			private static double s_last = -1.0;

			private static bool Prefix(ZDOMan __instance)
			{
				float num = (float)Configuration.sendIntervalMs.Value / 1000f;
				if (num <= 0f)
				{
					return true;
				}
				double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble;
				double num2 = ((s_last < 0.0) ? 0.0 : (realtimeSinceStartupAsDouble - s_last));
				s_last = realtimeSinceStartupAsDouble;
				List<ZDOPeer> peers = __instance.m_peers;
				int count = peers.Count;
				if (count == 0)
				{
					s_owed = 0.0;
					return false;
				}
				s_owed = Math.Min(s_owed + (double)count * num2 / (double)num, count);
				int num3 = (int)s_owed;
				s_owed -= num3;
				for (int i = 0; i < num3; i++)
				{
					if (s_next >= count)
					{
						s_next = 0;
					}
					__instance.SendZDOs(peers[s_next++], false);
				}
				return false;
			}
		}

		[HarmonyPatch(typeof(ZDOMan), "SendZDOs")]
		public static class ZDOMan_SendZDOs_Timing
		{
			private static void Prefix()
			{
				PerformanceStats.SendStarted();
			}

			private static void Postfix()
			{
				PerformanceStats.SendFinished();
			}
		}

		[HarmonyPatch]
		public static class PresentManager_RequestTargetFrameRate_Patch
		{
			private static int s_logged;

			private static int s_forced;

			private static bool s_looked;

			private static FieldInfo s_refreshRate;

			private static MethodInfo s_apply;

			private static IEnumerable<MethodBase> TargetMethods()
			{
				List<MethodBase> list = new List<MethodBase>();
				foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(typeof(PresentManager)))
				{
					ParameterInfo[] parameters = declaredMethod.GetParameters();
					if (declaredMethod.Name == "RequestTargetFrameRate" && parameters.Length >= 1 && parameters[0].ParameterType == typeof(int))
					{
						list.Add(declaredMethod);
					}
				}
				if (list.Count == 0)
				{
					Plugin.logger.LogWarning((object)"PresentManager.RequestTargetFrameRate(int, ...) is gone from this game version: the server keeps the frame rate the game asks for.");
				}
				return list;
			}

			private static void Prefix(ref int __0)
			{
				int num = __0;
				int value = Configuration.serverTargetFps.Value;
				if (value <= 0 || !Plugin.IsDedicated())
				{
					s_forced = 0;
					return;
				}
				int num2 = Mathf.Clamp(value, 30, 240);
				if (s_logged++ == 0 || num2 != num)
				{
					Plugin.logger.LogInfo((object)$"Server target frame rate: {num} -> {num2}");
				}
				__0 = num2;
				s_forced = num2;
			}

			private static void Postfix(PresentManager __instance)
			{
				if (s_forced > 0 && __instance != null)
				{
					if (!s_looked)
					{
						s_looked = true;
						s_refreshRate = typeof(PresentManager).GetField("m_requestedRefreshRate", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
						s_apply = typeof(PresentManager).GetMethod("UpdatePresentSettings", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					}
					if (!(s_refreshRate == null) && s_refreshRate.GetValue(__instance) is int num && num != s_forced)
					{
						s_refreshRate.SetValue(__instance, s_forced);
						s_apply?.Invoke(__instance, null);
					}
				}
			}
		}

		[HarmonyPatch(typeof(MonoUpdaters), "FixedUpdate")]
		public static class MonoUpdaters_FixedUpdate_Timing
		{
			private static void Prefix()
			{
				PerformanceStats.FixedStarted();
			}

			private static void Postfix()
			{
				PerformanceStats.FixedFinished();
			}
		}

		[HarmonyPatch(typeof(ZNet), "UpdatePeers")]
		public static class ZNet_UpdatePeers_Timing
		{
			private static void Prefix(out long __state)
			{
				__state = Stopwatch.GetTimestamp();
			}

			private static void Postfix(long __state)
			{
				PerformanceStats.Section(0, __state);
			}
		}

		[HarmonyPatch(typeof(ZDOMan), "Update")]
		public static class ZDOMan_Update_Timing
		{
			private static void Prefix(out long __state)
			{
				__state = Stopwatch.GetTimestamp();
			}

			private static void Postfix(long __state)
			{
				PerformanceStats.Section(1, __state);
			}
		}

		[HarmonyPatch(typeof(ZNetScene), "Update")]
		public static class ZNetScene_Update_Timing
		{
			private static void Prefix(out long __state)
			{
				__state = Stopwatch.GetTimestamp();
			}

			private static void Postfix(long __state)
			{
				PerformanceStats.Section(2, __state);
			}
		}

		[HarmonyPatch(typeof(MonoUpdaters), "Update")]
		public static class MonoUpdaters_Update_Timing
		{
			private static void Prefix(out long __state)
			{
				__state = Stopwatch.GetTimestamp();
			}

			private static void Postfix(long __state)
			{
				PerformanceStats.Section(5, __state);
			}
		}

		[HarmonyPatch(typeof(MonoUpdaters), "LateUpdate")]
		public static class MonoUpdaters_LateUpdate_Timing
		{
			private static void Prefix(out long __state)
			{
				__state = Stopwatch.GetTimestamp();
			}

			private static void Postfix(long __state)
			{
				PerformanceStats.Section(5, __state);
			}
		}

		[HarmonyPatch(typeof(ZNet), "SaveWorld")]
		public static class ZNet_SaveWorld_Timing
		{
			private static void Prefix(out long __state)
			{
				__state = Stopwatch.GetTimestamp();
			}

			private static void Postfix(long __state)
			{
				PerformanceStats.Section(6, __state);
			}
		}

		public bool FeatureEnabled()
		{
			if (Configuration.sendIntervalMs.Value <= 0 && Configuration.performanceStatsMinutes.Value <= 0)
			{
				return Configuration.serverTargetFps.Value > 0;
			}
			return true;
		}
	}
	public static class PerformanceStats
	{
		private const double SlowFrameSeconds = 0.1;

		private const int BucketMs = 5;

		public const int Peers = 0;

		public const int World = 1;

		public const int Objects = 2;

		public const int ZoneGen = 3;

		public const int Creatures = 4;

		public const int Updaters = 5;

		public const int Save = 6;

		private static readonly string[] SectionNames = new string[7] { "players' messages", "world updates", "objects", "zones", "creatures", "other game updates", "saving" };

		private static readonly double[] s_sections = new double[SectionNames.Length];

		private static readonly double[] s_worstSections = new double[SectionNames.Length];

		private static int s_worstGc0;

		private static int s_worstGc2;

		private static int s_worstSteps;

		private static int s_lastGc0;

		private static int s_lastGc2;

		private static readonly int[] s_histogram = new int[200];

		private static double s_lastFrame = -1.0;

		private static double s_nextReport = -1.0;

		private static double s_periodStart;

		private static int s_frames;

		private static int s_slowFrames;

		private static double s_frameSum;

		private static double s_frameMax;

		private static int s_stepsThisFrame;

		private static int s_stepSum;

		private static int s_stepMax;

		private static long s_fixedStarted;

		private static double s_fixedThisFrame;

		private static double s_fixedSum;

		private static double s_fixedMax;

		private static int s_sends;

		private static double s_sendSum;

		private static double s_sendMax;

		private static long s_sendStarted;

		private static int s_localZones;

		private static int s_ghostZones;

		private static double s_zoneSum;

		private static double s_zoneMax;

		private static bool Enabled => Configuration.performanceStatsMinutes.Value > 0;

		private static double Seconds(long ticks)
		{
			return (double)ticks / (double)Stopwatch.Frequency;
		}

		public static void FixedStep()
		{
			s_stepsThisFrame++;
		}

		public static void FixedStarted()
		{
			s_fixedStarted = Stopwatch.GetTimestamp();
		}

		public static void FixedFinished()
		{
			double num = Seconds(Stopwatch.GetTimestamp() - s_fixedStarted);
			s_fixedThisFrame += num;
			s_sections[4] += num;
		}

		public static void Section(int index, long started)
		{
			s_sections[index] += Seconds(Stopwatch.GetTimestamp() - started);
		}

		public static void SendStarted()
		{
			s_sendStarted = Stopwatch.GetTimestamp();
		}

		public static void SendFinished()
		{
			double num = Seconds(Stopwatch.GetTimestamp() - s_sendStarted);
			s_sends++;
			s_sendSum += num;
			s_sendMax = Math.Max(s_sendMax, num);
		}

		public static void Zones(int local, int ghost, long started)
		{
			if (local + ghost != 0)
			{
				double num = Seconds(Stopwatch.GetTimestamp() - started);
				s_sections[3] += num;
				s_localZones += local;
				s_ghostZones += ghost;
				s_zoneSum += num;
				s_zoneMax = Math.Max(s_zoneMax, num);
			}
		}

		public static void Frame()
		{
			int num = s_stepsThisFrame;
			double num2 = s_fixedThisFrame;
			s_stepsThisFrame = 0;
			s_fixedThisFrame = 0.0;
			if (!Enabled)
			{
				return;
			}
			double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble;
			if (s_lastFrame < 0.0)
			{
				s_lastFrame = realtimeSinceStartupAsDouble;
				s_periodStart = realtimeSinceStartupAsDouble;
				s_nextReport = realtimeSinceStartupAsDouble + (double)Configuration.performanceStatsMinutes.Value * 60.0;
				return;
			}
			double num3 = realtimeSinceStartupAsDouble - s_lastFrame;
			s_lastFrame = realtimeSinceStartupAsDouble;
			s_frames++;
			s_frameSum += num3;
			int num4 = GC.CollectionCount(0);
			int num5 = GC.CollectionCount(2);
			if (num3 > s_frameMax)
			{
				s_frameMax = num3;
				Array.Copy(s_sections, s_worstSections, s_sections.Length);
				s_worstGc0 = num4 - s_lastGc0;
				s_worstGc2 = num5 - s_lastGc2;
				s_worstSteps = num;
			}
			s_lastGc0 = num4;
			s_lastGc2 = num5;
			Array.Clear(s_sections, 0, s_sections.Length);
			if (num3 > 0.1)
			{
				s_slowFrames++;
			}
			s_histogram[Math.Min((int)(num3 * 1000.0 / 5.0), s_histogram.Length - 1)]++;
			s_stepSum += num;
			s_stepMax = Math.Max(s_stepMax, num);
			s_fixedSum += num2;
			s_fixedMax = Math.Max(s_fixedMax, num2);
			if (realtimeSinceStartupAsDouble >= s_nextReport)
			{
				Report(realtimeSinceStartupAsDouble - s_periodStart);
				s_periodStart = realtimeSinceStartupAsDouble;
				s_nextReport = realtimeSinceStartupAsDouble + (double)Configuration.performanceStatsMinutes.Value * 60.0;
			}
		}

		private static string WorstFrame()
		{
			double num = 0.0;
			List<string> list = new List<string>();
			for (int i = 0; i < SectionNames.Length; i++)
			{
				num += s_worstSections[i];
				if (s_worstSections[i] >= 0.001)
				{
					list.Add($"{SectionNames[i]} {1000.0 * s_worstSections[i]:0}");
				}
			}
			list.Add($"unaccounted {1000.0 * Math.Max(0.0, s_frameMax - num):0}");
			string text = ((s_worstGc2 > 0) ? $", full GC x{s_worstGc2}" : ((s_worstGc0 > 0) ? $", GC x{s_worstGc0}" : ""));
			return string.Format("Worst frame ({0:0} ms) spent, ms: {1}; {2} fixed step(s){3}.", 1000.0 * s_frameMax, string.Join(", ", list), s_worstSteps, text);
		}

		private static int Percentile(double share)
		{
			int num = (int)Math.Ceiling(share * (double)s_frames);
			int num2 = 0;
			for (int i = 0; i < s_histogram.Length; i++)
			{
				num2 += s_histogram[i];
				if (num2 >= num)
				{
					return (i + 1) * 5;
				}
			}
			return s_histogram.Length * 5;
		}

		private static void Report(double period)
		{
			int num = (Object.op_Implicit((Object)(object)ZNet.instance) ? ZNet.instance.GetPeers().Count : 0);
			if (s_frames > 0 && num > 0)
			{
				Plugin.logger.LogInfo((object)($"Performance over {period / 60.0:0.#} min, {num} player(s): " + $"{(double)s_frames / period:0.#} FPS, frame avg {1000.0 * s_frameSum / (double)s_frames:0} ms, median {Percentile(0.5)}, 95% {Percentile(0.95)}, 99% {Percentile(0.99)}, worst {1000.0 * s_frameMax:0} ms, {s_slowFrames} over {100.0:0} ms; " + $"fixed steps per frame avg {(double)s_stepSum / (double)s_frames:0.0}, max {s_stepMax}, their game logic {100.0 * s_fixedSum / period:0.0}% of the time, worst frame {1000.0 * s_fixedMax:0} ms; " + $"world sends {s_sends} ({(double)s_sends / period:0.#}/s), avg {((s_sends > 0) ? (1000.0 * s_sendSum / (double)s_sends) : 0.0):0.0} ms, worst {1000.0 * s_sendMax:0.0} ms, {100.0 * s_sendSum / period:0.0}% of the time; " + $"zones generated {s_localZones} (+{s_ghostZones} ghost), {1000.0 * s_zoneSum:0} ms in all, worst tick {1000.0 * s_zoneMax:0} ms. " + WorstFrame()));
			}
			Array.Clear(s_histogram, 0, s_histogram.Length);
			Array.Clear(s_worstSections, 0, s_worstSections.Length);
			s_worstGc0 = (s_worstGc2 = (s_worstSteps = 0));
			s_frames = (s_slowFrames = (s_stepSum = (s_stepMax = (s_sends = (s_localZones = (s_ghostZones = 0))))));
			s_frameSum = (s_frameMax = (s_sendSum = (s_sendMax = (s_zoneSum = (s_zoneMax = (s_fixedSum = (s_fixedMax = 0.0)))))));
		}
	}
}
namespace Serverbound.FeatureModel
{
	public interface IFeature
	{
		bool FeatureEnabled();
	}
	public class AvailableFeatures
	{
		public readonly List<IFeature> _features;

		public AvailableFeatures()
		{
			_features = new List<IFeature>();
		}

		public AvailableFeatures(List<IFeature> features)
		{
			_features = features;
		}

		public AvailableFeatures AddFeature(IFeature feature)
		{
			_features.Add(feature);
			return this;
		}

		public List<IFeature> EnabledFeatures()
		{
			return _features.Where((IFeature feature) => feature.FeatureEnabled()).ToList();
		}

		public Type[] GetAllNestedTypes()
		{
			return (from feature in EnabledFeatures()
				select feature.GetType().GetNestedTypes() into list
				from item in list
				select item).ToArray();
		}
	}
}
namespace Serverbound.Settings
{
	public class Configuration
	{
		public static ConfigEntry<bool> modEnabled;

		public static ConfigEntry<bool> maxObjectsPerFrameEnabled;

		public static ConfigEntry<int> maxObjectsPerFrame;

		public static ConfigEntry<bool> networkingEnabled;

		public static ConfigEntry<int> networkQueueSizeKB;

		public static ConfigEntry<int> networkSendRateMinKB;

		public static ConfigEntry<int> networkSendRateMaxKB;

		public static ConfigEntry<int> networkStatsMinutes;

		public static ConfigEntry<bool> consoleCommandsEnabled;

		public static ConfigEntry<int> unityJobWorkers;

		public static ConfigEntry<int> sendIntervalMs;

		public static ConfigEntry<int> maxCatchUpMs;

		public static ConfigEntry<int> maxZonesPerTick;

		public static ConfigEntry<int> performanceStatsMinutes;

		public static ConfigEntry<int> serverTargetFps;

		public static ConfigEntry<int> maxGiveAmount;

		public static ConfigEntry<int> consoleInputCodePage;

		public static ConfigEntry<bool> fixSaveClientChanges;

		public static ConfigEntry<bool> fixDungeonLoadGuard;

		public static ConfigEntry<bool> compatVcpSpawnQueue;

		public static ConfigEntry<bool> compatVcpUnload;

		public static ConfigEntry<bool> fixTeleportGhosts;

		public static void Load(ConfigFile config)
		{
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Expected O, but got Unknown
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Expected O, but got Unknown
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Expected O, but got Unknown
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Expected O, but got Unknown
			//IL_01a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b0: Expected O, but got Unknown
			//IL_01f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fc: Expected O, but got Unknown
			//IL_0223: Unknown result type (might be due to invalid IL or missing references)
			//IL_022d: Expected O, but got Unknown
			//IL_0250: Unknown result type (might be due to invalid IL or missing references)
			//IL_025a: Expected O, but got Unknown
			//IL_0281: Unknown result type (might be due to invalid IL or missing references)
			//IL_028b: Expected O, but got Unknown
			RemoveRetiredSettings(config);
			modEnabled = config.Bind<bool>("General", "Enabled", true, "Enable or disable the mod");
			maxObjectsPerFrameEnabled = config.Bind<bool>("MaxObjectsPerFrame", "Enabled", true, "Enable or disable the feature");
			maxObjectsPerFrame = config.Bind<int>("MaxObjectsPerFrame", "MaxObjects", 100, new ConfigDescription("Maximum number of objects the server can create per frame. A vanilla dedicated server creates 100 (the game's loading-screen rate: a server has no player of its own); lower values make areas fill in more slowly.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10000), Array.Empty<object>()));
			networkingEnabled = config.Bind<bool>("Networking", "Enabled", true, "Raise the limits on how fast the server sends world data to each player (server-side part of BetterNetworking). Needs a restart.");
			networkQueueSizeKB = config.Bind<int>("Networking", "QueueSizeKB", 48, new ConfigDescription("World data queued per player before sending pauses for that tick. A larger queue can delay new updates. Match the Steam send rate to the server's upload capacity and measure queue occupancy under load.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10, 80), Array.Empty<object>()));
			networkSendRateMinKB = config.Bind<int>("Networking", "SteamSendRateMinKB", 256, new ConfigDescription("Minimum rate Steam attempts to send to each player, KB/s. Valheim: 150. Keep it below the server's upload speed divided by the number of players.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(64, 4096), Array.Empty<object>()));
			networkSendRateMaxKB = config.Bind<int>("Networking", "SteamSendRateMaxKB", 1024, new ConfigDescription("Maximum rate Steam sends to each player, KB/s. Valheim: 150.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(64, 4096), Array.Empty<object>()));
			networkStatsMinutes = config.Bind<int>("Networking", "StatsIntervalMinutes", 5, "Every this many minutes, log per player how often their send queue was full. 0 disables.");
			consoleCommandsEnabled = config.Bind<bool>("Server", "ConsoleCommands", true, "Read commands from standard input: save, stop, players, give <item> <amount> <player>, broadcast <text>, event <name> <player>, events, help; anything else goes to the game's own console (kick, ban, unban, banned, stopevent, devcommands, skiptime ...). In AMP set App.HasWriteableConsole=True to type them into its console, and App.ExitMethod=String with App.ExitString=stop to shut down cleanly. On Windows also set [Logging.Console] Enabled = false in BepInEx.cfg, or BepInEx's own console takes over standard input.");
			maxGiveAmount = config.Bind<int>("Server", "MaxGiveAmount", 1000, "Most items one give command may drop.");
			consoleInputCodePage = config.Bind<int>("Server", "ConsoleInputCodePage", 1250, new ConfigDescription("How to read a console line that is not valid UTF-8 (UTF-8 is always tried first). Panels on Windows may write their code page instead: 1250 Central European (Czech), 852 Central European DOS, 1252 Western, 65001 UTF-8 only. The log says which bytes arrived the first time this is needed.", (AcceptableValueBase)(object)new AcceptableValueList<int>(new int[4] { 1250, 852, 1252, 65001 }), Array.Empty<object>()));
			unityJobWorkers = config.Bind<int>("Server", "UnityJobWorkers", 8, "Upper limit on Unity job worker threads. Unity starts one per CPU core, and on many-core hosts the idle ones still use CPU. Only ever lowers the count. 0 leaves Unity's default.");
			sendIntervalMs = config.Bind<int>("Performance", "SendIntervalMs", 100, new ConfigDescription("How often each player is sent world updates, in real milliseconds. Valheim sends to one player per frame, so each player waits players+1 frames: at 15 FPS with 4 players ~330 ms. Every send builds that player's list of nearby objects, so shorter intervals cost server CPU. 0 keeps Valheim's behaviour.", (AcceptableV