Decompiled source of HighSeat v0.2.2

plugins\HighSeat.dll

Decompiled 5 days ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using ConfigReload;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using PatchGuard;
using Steamworks;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("assembly_guiutils")]
[assembly: IgnoresAccessChecksTo("assembly_utils")]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("HighSeat")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("HighSeat: the server runs every creature near a player")]
[assembly: AssemblyFileVersion("0.2.2.0")]
[assembly: AssemblyInformationalVersion("0.2.2+c059968f7e2f4c97229c435352912d3c276ffd65")]
[assembly: AssemblyProduct("HighSeat")]
[assembly: AssemblyTitle("HighSeat")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.2.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.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace HighSeat
{
	[HarmonyPatch(typeof(ZNetScene), "OutsideActiveArea", new Type[] { typeof(Vector3) })]
	internal static class ActiveArea
	{
		private static void Postfix(Vector3 point, ref bool __result)
		{
			//IL_002e: 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)
			if (!__result || !ServerRun.Active)
			{
				return;
			}
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				if (peer.IsReady() && ZNetScene.InActiveArea(point, peer.GetRefPos()))
				{
					__result = false;
					break;
				}
			}
		}
	}
	[HarmonyPatch(typeof(BaseAI), "Awake")]
	internal static class AlwaysAnimate
	{
		private static void Postfix(BaseAI __instance)
		{
			if (ServerRun.Active)
			{
				Animator[] componentsInChildren = ((Component)__instance).GetComponentsInChildren<Animator>(true);
				for (int i = 0; i < componentsInChildren.Length; i++)
				{
					componentsInChildren[i].cullingMode = (AnimatorCullingMode)0;
				}
			}
		}
	}
	internal static class FrameGuard
	{
		private const float Smoothing = 0.05f;

		private static float _average = 1f / 60f;

		private static bool _warned;

		public static bool Busy { get; private set; }

		public static void Measure()
		{
			if (ServerRun.Active)
			{
				_average += (Time.unscaledDeltaTime - _average) * 0.05f;
				bool num = _average * 1000f > (float)Settings.FrameBudgetMs.Value;
				if (num && !_warned)
				{
					HighSeat.Log.LogWarning((object)($"server frames average {_average * 1000f:0} ms, over the {Settings.FrameBudgetMs.Value} ms " + "budget: new creatures are left to players until it recovers"));
				}
				_warned = num;
				Busy = num;
			}
		}
	}
	[BepInPlugin("com.HighSeat", "HighSeat", "0.2.2")]
	public class HighSeat : BaseUnityPlugin
	{
		public const string PluginGuid = "com.HighSeat";

		public const string PluginName = "HighSeat";

		public const string PluginVersion = "0.2.2";

		public static ManualLogSource Log { get; private set; }

		private void Awake()
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Settings.Bind(((BaseUnityPlugin)this).Config);
			ConfigReloader.Setup(((BaseUnityPlugin)this).Config, ((BaseUnityPlugin)this).Logger);
			Harmony val = new Harmony("com.HighSeat");
			val.PatchAll(Assembly.GetExecutingAssembly());
			Guard.Install(val, ((BaseUnityPlugin)this).Logger, Assembly.GetExecutingAssembly());
		}

		private void Update()
		{
			FrameGuard.Measure();
		}
	}
	[HarmonyPatch(typeof(ZDOMan), "ReleaseNearbyZDOS")]
	internal static class Ownership
	{
		private static readonly List<ZDO> Near = new List<ZDO>();

		private static bool Prefix(ZDOMan __instance, Vector3 refPosition, long uid)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: 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)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: 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_0083: Unknown result type (might be due to invalid IL or missing references)
			if (!ServerRun.Active)
			{
				return true;
			}
			Vector2s zone = ZoneSystem.GetZone(refPosition);
			SimulationDistance syncedSimulationDistance = ZNet.instance.GetSyncedSimulationDistance();
			Near.Clear();
			__instance.FindSectorObjects(zone, new SimulationDistance(((SimulationDistance)(ref syncedSimulationDistance)).NearSimulationDistance, 0, ((SimulationDistance)(ref syncedSimulationDistance)).IsClassic), Near, (List<ZDO>)null);
			long sessionID = ZDOMan.GetSessionID();
			bool take = !FrameGuard.Busy;
			foreach (ZDO item in Near)
			{
				if (item.Persistent && !Claim(item, sessionID, take))
				{
					Vanilla(__instance, item, zone, uid);
				}
			}
			Near.Clear();
			return false;
		}

		private static bool Claim(ZDO zdo, long server, bool take)
		{
			if (!ServerRun.Is(zdo))
			{
				return false;
			}
			if (zdo.GetOwner() == server)
			{
				return true;
			}
			if (!take)
			{
				return false;
			}
			zdo.SetOwner(server);
			return true;
		}

		private static void Vanilla(ZDOMan man, ZDO zdo, Vector2s zone, long uid)
		{
			//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_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: 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_003a: 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)
			Vector3 position = zdo.GetPosition();
			if (zdo.GetOwner() == uid)
			{
				if (!ZNetScene.InActiveArea(position, zone))
				{
					zdo.SetOwner(0L);
				}
			}
			else if ((!zdo.HasOwner() || !man.IsInPeerActiveArea(position, zdo.GetOwner())) && ZNetScene.InActiveArea(position, zone))
			{
				zdo.SetOwner(uid);
			}
		}
	}
	[HarmonyPatch(typeof(ZNetScene), "CreateDestroyObjects")]
	internal static class PlayerAreas
	{
		private static List<ZDO> Near = null;

		private static List<ZDO> Distant = null;

		private static readonly HashSet<SectorIndex> Visited = new HashSet<SectorIndex>();

		private static bool Prefix(ZNetScene __instance)
		{
			if (!ServerRun.Active)
			{
				return true;
			}
			Near = __instance.m_tempCurrentObjects;
			Distant = __instance.m_tempCurrentDistantObjects;
			Near.Clear();
			Distant.Clear();
			Collect();
			__instance.CreateObjects(Near, Distant);
			__instance.RemoveObjects(Near, Distant);
			return false;
		}

		private static void Collect()
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: 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_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: 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_005a: 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_0087: Unknown result type (might be due to invalid IL or missing references)
			Visited.Clear();
			SimulationDistance syncedSimulationDistance = ZNet.instance.GetSyncedSimulationDistance();
			Vector2s zone = ZoneSystem.GetZone(ZNet.instance.GetReferencePosition());
			AddNear(zone, syncedSimulationDistance, groundFirst: false);
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				if (peer.IsReady())
				{
					AddNear(ZoneSystem.GetZone(peer.GetRefPos()), syncedSimulationDistance, groundFirst: true);
				}
			}
			if (!ZNet.instance.IsDedicated())
			{
				AddDistant(zone, syncedSimulationDistance);
			}
		}

		private static void AddNear(Vector2s center, SimulationDistance sim, bool groundFirst)
		{
			//IL_0014: 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_003d: 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_0065: 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)
			int nearSimulationDistance = ((SimulationDistance)(ref sim)).NearSimulationDistance;
			Vector2s val = default(Vector2s);
			for (int i = -nearSimulationDistance; i <= nearSimulationDistance; i++)
			{
				for (int j = -nearSimulationDistance; j <= nearSimulationDistance; j++)
				{
					((Vector2s)(ref val))..ctor(center.x + j, center.y + i);
					if (((j == 0 && i == 0) || ((SimulationDistance)(ref sim)).IsClassic || ZoneSystem.instance.ZonesWithinRadius(center, val, nearSimulationDistance, false)) && (!groundFirst || ZoneSystem.instance.m_zones.ContainsKey(val)))
					{
						ZDOMan.instance.FindObjects(val, Near, Visited);
					}
				}
			}
		}

		private static void AddDistant(Vector2s center, SimulationDistance sim)
		{
			//IL_0014: 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_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: 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)
			int totalSimulationDistance = ((SimulationDistance)(ref sim)).TotalSimulationDistance;
			Vector2s val = default(Vector2s);
			for (int i = -totalSimulationDistance; i <= totalSimulationDistance; i++)
			{
				for (int j = -totalSimulationDistance; j <= totalSimulationDistance; j++)
				{
					((Vector2s)(ref val))..ctor(center.x + j, center.y + i);
					if (((SimulationDistance)(ref sim)).IsClassic || ZoneSystem.instance.ZonesWithinRadius(center, val, totalSimulationDistance, true))
					{
						ZDOMan.instance.FindDistantObjects(val, Distant, Visited);
					}
				}
			}
		}
	}
	[HarmonyPatch(typeof(ZSteamSocket), "RegisterGlobalCallbacks")]
	internal static class SendRate
	{
		private const int Rate = 512000;

		private static bool GameServer => Application.isBatchMode;

		private static void Postfix()
		{
			try
			{
				Raise();
			}
			catch (Exception ex)
			{
				HighSeat.Log.LogWarning((object)("send rate left as it was: " + ex.Message));
			}
		}

		private static void Raise()
		{
			IntPtr intPtr = Marshal.AllocHGlobal(4);
			try
			{
				if (Current(intPtr) < 512000)
				{
					Marshal.WriteInt32(intPtr, 512000);
					Set((ESteamNetworkingConfigValue)10, intPtr);
					Set((ESteamNetworkingConfigValue)11, intPtr);
				}
			}
			finally
			{
				Marshal.FreeHGlobal(intPtr);
			}
		}

		private static int Current(IntPtr data)
		{
			//IL_0005: 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)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Invalid comparison between Unknown and I4
			ulong num = 4uL;
			ESteamNetworkingConfigValue val = (ESteamNetworkingConfigValue)11;
			ESteamNetworkingConfigScope val2 = (ESteamNetworkingConfigScope)1;
			ESteamNetworkingConfigDataType val3 = default(ESteamNetworkingConfigDataType);
			if ((int)(GameServer ? SteamGameServerNetworkingUtils.GetConfigValue(val, val2, IntPtr.Zero, ref val3, data, ref num) : SteamNetworkingUtils.GetConfigValue(val, val2, IntPtr.Zero, ref val3, data, ref num)) != 1)
			{
				return 0;
			}
			return Marshal.ReadInt32(data);
		}

		private static void Set(ESteamNetworkingConfigValue value, IntPtr data)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: 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_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			ESteamNetworkingConfigScope val = (ESteamNetworkingConfigScope)1;
			ESteamNetworkingConfigDataType val2 = (ESteamNetworkingConfigDataType)1;
			if (GameServer)
			{
				SteamGameServerNetworkingUtils.SetConfigValue(value, val, IntPtr.Zero, val2, data);
			}
			else
			{
				SteamNetworkingUtils.SetConfigValue(value, val, IntPtr.Zero, val2, data);
			}
		}
	}
	internal static class ServerRun
	{
		private static readonly Dictionary<int, bool> Kinds = new Dictionary<int, bool>();

		public static bool Active
		{
			get
			{
				if (Settings.ServerRunsCreatures.Value && (Object)(object)ZNet.instance != (Object)null)
				{
					return ZNet.instance.IsServer();
				}
				return false;
			}
		}

		public static bool Is(ZDO zdo)
		{
			int prefab = zdo.GetPrefab();
			if (!Kinds.TryGetValue(prefab, out var value))
			{
				value = Decide(prefab);
				Kinds[prefab] = value;
			}
			return value;
		}

		private static bool Decide(int prefab)
		{
			GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(prefab) : null);
			if ((Object)(object)val == (Object)null || (Object)(object)val.GetComponent<Player>() != (Object)null || (Object)(object)val.GetComponent<Fish>() != (Object)null)
			{
				return false;
			}
			if (!((Object)(object)val.GetComponent<BaseAI>() != (Object)null) && !((Object)(object)val.GetComponent<SpawnSystem>() != (Object)null) && !((Object)(object)val.GetComponent<CreatureSpawner>() != (Object)null) && !((Object)(object)val.GetComponent<SpawnArea>() != (Object)null) && !((Object)(object)val.GetComponent<TriggerSpawner>() != (Object)null))
			{
				return (Object)(object)val.GetComponent<OfferingBowl>() != (Object)null;
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(SpawnSystem), "UpdateSpawning")]
	internal static class ServerSpawning
	{
		private static List<Player> InZone => SpawnSystem.m_tempNearPlayers;

		private static bool Prefix(SpawnSystem __instance)
		{
			if (!ServerRun.Active)
			{
				return true;
			}
			if (__instance.m_nview.IsValid() && __instance.m_nview.IsOwner())
			{
				InZone.Clear();
				__instance.GetPlayersInZone(InZone);
				if (InZone.Count > 0)
				{
					Spawn(__instance, ZNet.instance.GetTime());
				}
				InZone.Clear();
			}
			return false;
		}

		private static void Spawn(SpawnSystem system, DateTime time)
		{
			foreach (SpawnSystemList spawnList in system.m_spawnLists)
			{
				system.UpdateSpawnList(spawnList.m_spawners, time, false, "b_");
			}
			List<SpawnData> list = RaidSpawners();
			if (list != null)
			{
				system.UpdateSpawnList(list, time, true, "e_");
			}
			for (int i = 0; i < system.m_heightmap.m_cornerAltBiomes.Count; i++)
			{
				AltBiome val = system.m_heightmap.m_cornerAltBiomes[i];
				if (val.m_spawn.Count > 0)
				{
					system.UpdateSpawnList(val.m_spawn, time, false, $"m{i}");
				}
			}
		}

		private static List<SpawnData> RaidSpawners()
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			RandEventSystem instance = RandEventSystem.instance;
			RandomEvent val = instance.m_forcedEvent ?? instance.m_randomEvent;
			if (val == null || val.m_time <= val.m_spawnerDelay)
			{
				return null;
			}
			if (instance.m_forcedEvent != null)
			{
				return val.m_spawn;
			}
			foreach (Player item in InZone)
			{
				if (instance.IsInsideRandomEventArea(val, ((Component)item).transform.position))
				{
					return val.m_spawn;
				}
			}
			return null;
		}
	}
	internal static class Settings
	{
		private const string General = "1 - General";

		public static ConfigEntry<bool> ServerRunsCreatures;

		public static ConfigEntry<int> FrameBudgetMs;

		public static void Bind(ConfigFile config)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Expected O, but got Unknown
			ServerRunsCreatures = config.Bind<bool>("1 - General", "Server runs creatures", true, "The server loads every player's area and runs every creature and spawner in it, tamed creatures and bosses included. Off: the game's own rule, where the nearest player's game runs each creature.");
			FrameBudgetMs = config.Bind<int>("1 - General", "Server frame budget ms", 50, new ConfigDescription("While the server's frames take longer than this, creatures it does not run yet are left to players as the game does; those it runs stay with it.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(20, 500), Array.Empty<object>()));
		}
	}
	[HarmonyPatch(typeof(ZoneSystem), "UpdateTTL")]
	internal static class ZoneKeeping
	{
		private static void Prefix(ZoneSystem __instance)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			if (!ServerRun.Active)
			{
				return;
			}
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				if (peer.IsReady())
				{
					__instance.CreateLocalZones(peer.GetRefPos());
				}
			}
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
	[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;
		}
	}
}
namespace ConfigReload
{
	internal static class ConfigReloader
	{
		private sealed class Poller : IDisposable
		{
			private const int IntervalMilliseconds = 5000;

			private readonly ConfigFile config;

			private readonly ManualLogSource? log;

			private readonly string fileName;

			private readonly Timer timer;

			private DateTime lastWriteTime;

			private long lastLength;

			private string lastContent;

			private int reloading;

			public Poller(ConfigFile config, ManualLogSource? log)
			{
				this.config = config;
				this.log = log;
				fileName = Path.GetFileName(config.ConfigFilePath);
				Snapshot(out lastWriteTime, out lastLength);
				lastContent = ReadOrNull(config.ConfigFilePath) ?? "";
				timer = new Timer(delegate
				{
					Poll();
				}, null, 5000, 5000);
			}

			private void Poll()
			{
				try
				{
					if (SnapshotChanged())
					{
						string text = ReadOrNull(config.ConfigFilePath);
						if (text != null && !(text == lastContent))
						{
							lastContent = text;
							ScheduleCheck();
						}
					}
				}
				catch (Exception ex)
				{
					ManualLogSource? obj = log;
					if (obj != null)
					{
						obj.LogError((object)("Watching " + fileName + " failed: " + ex.Message));
					}
				}
			}

			private bool SnapshotChanged()
			{
				Snapshot(out var writeTime, out var length);
				if (writeTime == lastWriteTime && length == lastLength)
				{
					return false;
				}
				lastWriteTime = writeTime;
				lastLength = length;
				return true;
			}

			private void ScheduleCheck()
			{
				if (Interlocked.CompareExchange(ref reloading, 1, 0) == 0)
				{
					ThreadingHelper.SynchronizingObject.BeginInvoke(new Action(CheckAndReload), null);
				}
			}

			private void CheckAndReload()
			{
				try
				{
					if (!LoadedValues.Matches(config, lastContent))
					{
						ManualLogSource? obj = log;
						if (obj != null)
						{
							obj.LogInfo((object)(fileName + " changed on disk, reloading"));
						}
						ReloadQuietly();
					}
				}
				catch (Exception ex)
				{
					ManualLogSource? obj2 = log;
					if (obj2 != null)
					{
						obj2.LogError((object)("Failed to reload " + fileName + ", please check it for typos and formatting: " + ex.Message));
					}
				}
				finally
				{
					Resnapshot();
					Interlocked.Exchange(ref reloading, 0);
				}
			}

			private void ReloadQuietly()
			{
				bool saveOnConfigSet = config.SaveOnConfigSet;
				config.SaveOnConfigSet = false;
				try
				{
					config.Reload();
				}
				finally
				{
					config.SaveOnConfigSet = saveOnConfigSet;
				}
				if (saveOnConfigSet)
				{
					config.Save();
				}
			}

			private void Resnapshot()
			{
				Snapshot(out lastWriteTime, out lastLength);
				lastContent = ReadOrNull(config.ConfigFilePath) ?? lastContent;
			}

			private void Snapshot(out DateTime writeTime, out long length)
			{
				FileInfo fileInfo = new FileInfo(config.ConfigFilePath);
				if (fileInfo.Exists)
				{
					writeTime = fileInfo.LastWriteTimeUtc;
					length = fileInfo.Length;
				}
				else
				{
					writeTime = DateTime.MinValue;
					length = -1L;
				}
			}

			private static string? ReadOrNull(string path)
			{
				try
				{
					return File.Exists(path) ? File.ReadAllText(path) : null;
				}
				catch (IOException)
				{
					return null;
				}
			}

			public void Dispose()
			{
				timer.Dispose();
			}
		}

		private static readonly HashSet<ConfigFile> held = new HashSet<ConfigFile>();

		public static void HoldSaves(ConfigFile config)
		{
			if (config.SaveOnConfigSet && held.Add(config))
			{
				config.SaveOnConfigSet = false;
			}
		}

		public static IDisposable Setup(ConfigFile config, ManualLogSource? log = null, bool saveNow = true)
		{
			if (held.Remove(config))
			{
				config.SaveOnConfigSet = true;
			}
			if (saveNow)
			{
				config.Save();
			}
			return new Poller(config, log);
		}
	}
	internal static class LoadedValues
	{
		private static readonly PropertyInfo? orphans = typeof(ConfigFile).GetProperty("OrphanedEntries", BindingFlags.Instance | BindingFlags.NonPublic);

		public static bool Matches(ConfigFile config, string content)
		{
			Dictionary<ConfigDefinition, string> dictionary = Parse(content);
			Dictionary<ConfigDefinition, string> dictionary2 = Loaded(config);
			if (dictionary2 == null)
			{
				return false;
			}
			if (dictionary.Count != dictionary2.Count)
			{
				return false;
			}
			foreach (KeyValuePair<ConfigDefinition, string> item in dictionary)
			{
				if (!dictionary2.TryGetValue(item.Key, out var value) || !string.Equals(value, item.Value, StringComparison.Ordinal))
				{
					return false;
				}
			}
			return true;
		}

		private static Dictionary<ConfigDefinition, string>? Loaded(ConfigFile config)
		{
			if (!(orphans?.GetValue(config) is Dictionary<ConfigDefinition, string> dictionary))
			{
				return null;
			}
			Dictionary<ConfigDefinition, string> dictionary2 = new Dictionary<ConfigDefinition, string>(dictionary);
			foreach (KeyValuePair<ConfigDefinition, ConfigEntryBase> item in config)
			{
				dictionary2[item.Key] = item.Value.GetSerializedValue();
			}
			return dictionary2;
		}

		private static Dictionary<ConfigDefinition, string> Parse(string content)
		{
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Expected O, but got Unknown
			Dictionary<ConfigDefinition, string> dictionary = new Dictionary<ConfigDefinition, string>();
			string text = "";
			string[] array = content.Split(new char[1] { '\n' });
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim();
				if (text2.Length == 0 || text2.StartsWith("#", StringComparison.Ordinal))
				{
					continue;
				}
				if (text2.StartsWith("[", StringComparison.Ordinal) && text2.EndsWith("]", StringComparison.Ordinal))
				{
					text = text2.Substring(1, text2.Length - 2);
					continue;
				}
				int num = text2.IndexOf('=');
				if (num > 0)
				{
					dictionary[new ConfigDefinition(text, text2.Substring(0, num).Trim())] = text2.Substring(num + 1).Trim();
				}
			}
			return dictionary;
		}
	}
}
namespace PatchGuard
{
	internal static class Guard
	{
		private const string Marker = "PatchGuard.Logged";

		private static ManualLogSource? log;

		private static Assembly? modAssembly;

		private static string[] namespaces = Array.Empty<string>();

		public static int Install(Harmony harmony, ManualLogSource logger, Assembly assembly)
		{
			log = logger;
			modAssembly = assembly;
			namespaces = RootNamespaces(assembly);
			return 0;
		}

		public static void Run(string context, Action action)
		{
			try
			{
				action();
			}
			catch (Exception exception)
			{
				Report(exception, context);
				throw;
			}
		}

		public static T Run<T>(string context, Func<T> func)
		{
			try
			{
				return func();
			}
			catch (Exception exception)
			{
				Report(exception, context);
				throw;
			}
		}

		public static void Run<TArg>(string context, Action<TArg> action, TArg arg)
		{
			try
			{
				action(arg);
			}
			catch (Exception exception)
			{
				Report(exception, context);
				throw;
			}
		}

		public static void Run<T1, T2>(string context, Action<T1, T2> action, T1 first, T2 second)
		{
			try
			{
				action(first, second);
			}
			catch (Exception exception)
			{
				Report(exception, context);
				throw;
			}
		}

		public static T Run<TArg, T>(string context, Func<TArg, T> func, TArg arg)
		{
			try
			{
				return func(arg);
			}
			catch (Exception exception)
			{
				Report(exception, context);
				throw;
			}
		}

		public static T Run<T1, T2, T>(string context, Func<T1, T2, T> func, T1 first, T2 second)
		{
			try
			{
				return func(first, second);
			}
			catch (Exception exception)
			{
				Report(exception, context);
				throw;
			}
		}

		public static Action Wrap(string context, Action action)
		{
			return delegate
			{
				Run(context, action);
			};
		}

		public static EventHandler Wrap(string context, EventHandler handler)
		{
			return delegate(object sender, EventArgs args)
			{
				Run(context, delegate
				{
					handler(sender, args);
				});
			};
		}

		public static void Report(Exception exception, string context)
		{
			if (log != null && !exception.Data.Contains("PatchGuard.Logged") && IsFromMod(exception))
			{
				try
				{
					exception.Data["PatchGuard.Logged"] = true;
				}
				catch (Exception)
				{
				}
				log.LogError((object)$"Exception in {context} (thrown by this mod's code):\n{exception}");
			}
		}

		internal static bool HasPatchFrom(MethodBase method, Assembly assembly)
		{
			Patches patchInfo = Harmony.GetPatchInfo(method);
			return patchInfo?.Prefixes.Concat(patchInfo.Postfixes).Concat(patchInfo.Transpilers).Concat(patchInfo.Finalizers)
				.Any((Patch p) => p.PatchMethod.DeclaringType?.Assembly == assembly) ?? false;
		}

		private static bool IsFromMod(Exception exception)
		{
			for (Exception ex = exception; ex != null; ex = ex.InnerException)
			{
				StackFrame[] array = null;
				try
				{
					array = new StackTrace(ex, fNeedFileInfo: false).GetFrames();
				}
				catch (Exception)
				{
				}
				if (array != null && array.Any((StackFrame f) => f.GetMethod()?.DeclaringType?.Assembly == modAssembly))
				{
					return true;
				}
				string text = ex.StackTrace;
				if (text != null && namespaces.Any((string ns) => text.Contains(" " + ns + ".") || text.Contains("<" + ns + ".")))
				{
					return true;
				}
			}
			return false;
		}

		private static string[] RootNamespaces(Assembly assembly)
		{
			try
			{
				return (from t in assembly.GetTypes()
					select t.Namespace into ns
					where !string.IsNullOrEmpty(ns)
					select ns.Split(new char[1] { '.' })[0]).Distinct().ToArray();
			}
			catch (ReflectionTypeLoadException ex)
			{
				return (from t in ex.Types
					where t?.Namespace != null
					select t.Namespace.Split(new char[1] { '.' })[0]).Distinct().ToArray();
			}
		}

		internal static string Describe(MethodBase method)
		{
			return method.DeclaringType?.Name + "." + method.Name;
		}
	}
	internal static class Profiler
	{
		private sealed class Sample
		{
			public long Ticks;

			public long Calls;
		}

		private static ManualLogSource? log;

		private static readonly HashSet<MethodBase> profiled = new HashSet<MethodBase>();

		private static readonly HashSet<MethodBase> ownPatches = new HashSet<MethodBase>();

		private static readonly ConcurrentDictionary<MethodBase, Sample> samples = new ConcurrentDictionary<MethodBase, Sample>();

		private static int intervalSeconds;

		[ThreadStatic]
		private static Stack<long>? starts;

		public static IDisposable Install(Harmony harmony, ManualLogSource logger, Assembly assembly, int reportEverySeconds = 5, int top = 12)
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Expected O, but got Unknown
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Expected O, but got Unknown
			log = logger;
			intervalSeconds = reportEverySeconds;
			HarmonyMethod prefix = new HarmonyMethod(typeof(Profiler), "Prefix", (Type[])null)
			{
				priority = 800
			};
			HarmonyMethod postfix = new HarmonyMethod(typeof(Profiler), "Postfix", (Type[])null)
			{
				priority = 0
			};
			int num = TimeGameMethods(harmony, assembly, prefix, postfix);
			logger.LogInfo((object)string.Format(arg1: TimeOwnPatches(harmony, assembly, prefix, postfix), format: "PatchGuard profiler: timing {0} patched game methods and {1} of the mod's own patch methods, report every {2} s", arg0: num, arg2: reportEverySeconds));
			return new Timer(delegate
			{
				Report(top);
			}, null, reportEverySeconds * 1000, reportEverySeconds * 1000);
		}

		private static int TimeGameMethods(Harmony harmony, Assembly assembly, HarmonyMethod prefix, HarmonyMethod postfix)
		{
			int num = 0;
			foreach (MethodBase item in Harmony.GetAllPatchedMethods().ToList())
			{
				if (!profiled.Contains(item) && Guard.HasPatchFrom(item, assembly) && TryTime(harmony, item, prefix, postfix, ""))
				{
					profiled.Add(item);
					num++;
				}
			}
			return num;
		}

		private static int TimeOwnPatches(Harmony harmony, Assembly assembly, HarmonyMethod prefix, HarmonyMethod postfix)
		{
			int num = 0;
			foreach (MethodInfo item in OwnPatchMethods(assembly))
			{
				if (ownPatches.Add(item) && TryTime(harmony, item, prefix, postfix, "patch "))
				{
					num++;
				}
			}
			return num;
		}

		private static IEnumerable<MethodInfo> OwnPatchMethods(Assembly assembly)
		{
			foreach (MethodBase item in profiled.ToList())
			{
				Patches patchInfo = Harmony.GetPatchInfo(item);
				if (patchInfo == null)
				{
					continue;
				}
				foreach (Patch item2 in patchInfo.Prefixes.Concat(patchInfo.Postfixes).Concat(patchInfo.Finalizers))
				{
					MethodInfo patchMethod = item2.PatchMethod;
					if (patchMethod.DeclaringType?.Assembly == assembly && patchMethod.DeclaringType != typeof(Profiler))
					{
						yield return patchMethod;
					}
				}
			}
		}

		private static bool TryTime(Harmony harmony, MethodBase method, HarmonyMethod prefix, HarmonyMethod postfix, string kind)
		{
			try
			{
				harmony.Patch(method, prefix, postfix, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				return true;
			}
			catch (Exception ex)
			{
				ManualLogSource? obj = log;
				if (obj != null)
				{
					obj.LogWarning((object)("PatchGuard profiler: could not time " + kind + Guard.Describe(method) + ": " + ex.Message));
				}
				return false;
			}
		}

		private static void Prefix()
		{
			if (starts == null)
			{
				starts = new Stack<long>();
			}
			starts.Push(Stopwatch.GetTimestamp());
		}

		private static void Postfix(MethodBase __originalMethod)
		{
			if (starts != null && starts.Count != 0)
			{
				long value = Stopwatch.GetTimestamp() - starts.Pop();
				Sample orAdd = samples.GetOrAdd(__originalMethod, (MethodBase _) => new Sample());
				Interlocked.Add(ref orAdd.Ticks, value);
				Interlocked.Increment(ref orAdd.Calls);
			}
		}

		private static void Report(int top)
		{
			try
			{
				KeyValuePair<MethodBase, Sample>[] array = samples.ToArray();
				samples.Clear();
				if (array.Length != 0)
				{
					double toMs = 1000.0 / (double)Stopwatch.Frequency;
					double num = (double)array.Sum((KeyValuePair<MethodBase, Sample> kv) => kv.Value.Ticks) * toMs;
					IEnumerable<string> values = from kv in array.OrderByDescending((KeyValuePair<MethodBase, Sample> kv) => kv.Value.Ticks).Take(top)
						select string.Format("{0,8:0.0} ms  {1}{2}  x{3}", (double)kv.Value.Ticks * toMs, ownPatches.Contains(kv.Key) ? "[mod patch] " : "", Guard.Describe(kv.Key), kv.Value.Calls);
					ManualLogSource? obj = log;
					if (obj != null)
					{
						obj.LogInfo((object)($"PatchGuard profile: {num:0} ms inside patched methods in the last {intervalSeconds} s ({num / ((double)intervalSeconds * 10.0):0.0}% of wall time)\n  " + string.Join("\n  ", values)));
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource? obj2 = log;
				if (obj2 != null)
				{
					obj2.LogWarning((object)("PatchGuard profiler report failed: " + ex.Message));
				}
			}
		}
	}
}