Decompiled source of LeanHeim v0.5.2

plugins/LeanHeim.dll

Decompiled 4 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using LeanHeim.Patches;
using Microsoft.CodeAnalysis;
using Unity.Profiling;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Scripting;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("0.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace LeanHeim
{
	[BepInPlugin("leanheim.performance", "LeanHeim", "0.5.2")]
	public class Plugin : BaseUnityPlugin
	{
		public const string Guid = "leanheim.performance";

		public const string Name = "LeanHeim";

		public const string Version = "0.5.2";

		internal static ManualLogSource Log;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Invalid comparison between Unknown and I4
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Settings.Bind(((BaseUnityPlugin)this).Config);
			if ((int)SystemInfo.graphicsDeviceType == 4)
			{
				Log.LogInfo((object)"Headless (no graphics device) - LeanHeim does nothing here.");
				return;
			}
			_harmony = new Harmony("leanheim.performance");
			if (Settings.ProfilerEnabled.Value)
			{
				ProfilerPatches.Install(_harmony);
			}
			Patch(typeof(WeatherPatches), Settings.WeatherEnabled.Value);
			Patch(typeof(LightPatches), Settings.LightsEnabled.Value);
			Patch(typeof(RenderingPatches), Settings.RenderingEnabled.Value);
			Patch(typeof(GpuPatches), Settings.GpuEnabled.Value);
			Patch(typeof(CharacterPatches), Settings.CharactersEnabled.Value);
			Patch(typeof(WearNTearPatches), Settings.BuildingsEnabled.Value);
			Patch(typeof(MemoryPatches), Settings.MemoryEnabled.Value);
			if (Settings.GpuEnabled.Value)
			{
				Settings.ReflectionInterval.SettingChanged += delegate
				{
					GpuPatches.Reapply();
				};
				Settings.ReflectionResolution.SettingChanged += delegate
				{
					GpuPatches.Reapply();
				};
				Settings.TextureMipmapLimit.SettingChanged += delegate
				{
					GpuPatches.Reapply();
				};
				Settings.LodBiasScale.SettingChanged += delegate
				{
					GpuPatches.Reapply();
				};
				Settings.ShadowCascades.SettingChanged += delegate
				{
					GpuPatches.Reapply();
				};
			}
			if (Settings.MemoryEnabled.Value)
			{
				MemoryPatches.ApplyGc();
				Settings.GcTimeSliceMs.SettingChanged += delegate
				{
					MemoryPatches.ApplyGc();
				};
			}
			Settings.WeatherDensity.SettingChanged += delegate
			{
				WeatherPatches.ReapplyAll();
			};
			Settings.WeatherNoCollision.SettingChanged += delegate
			{
				WeatherPatches.ReapplyAll();
			};
			Settings.WeatherNoLights.SettingChanged += delegate
			{
				WeatherPatches.ReapplyAll();
			};
			Settings.WeatherNoTrails.SettingChanged += delegate
			{
				WeatherPatches.ReapplyAll();
			};
			Settings.SmallObjectDistance.SettingChanged += delegate
			{
				RenderingPatches.ApplyCullDistances();
			};
			Settings.ParticleRaycastBudget.SettingChanged += delegate
			{
				RenderingPatches.ApplyQuality();
			};
			Settings.MaxShadowDistance.SettingChanged += delegate
			{
				RenderingPatches.ApplyQuality();
			};
		}

		private void Patch(Type patchClass, bool enabled)
		{
			if (enabled)
			{
				_harmony.CreateClassProcessor(patchClass).Patch();
				Log.LogInfo((object)("Enabled module " + patchClass.Name));
			}
		}

		private void Update()
		{
			if (_harmony != null)
			{
				if (Settings.MemoryEnabled.Value)
				{
					MemoryPatches.Tick();
				}
				if (Settings.ProfilerEnabled.Value)
				{
					ProfilerPatches.Tick();
				}
			}
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal static class Settings
	{
		public static ConfigEntry<bool> ProfilerEnabled;

		public static ConfigEntry<float> ProfilerInterval;

		public static ConfigEntry<bool> WeatherEnabled;

		public static ConfigEntry<float> WeatherDensity;

		public static ConfigEntry<bool> WeatherNoCollision;

		public static ConfigEntry<bool> WeatherNoLights;

		public static ConfigEntry<bool> WeatherNoTrails;

		public static ConfigEntry<bool> LightsEnabled;

		public static ConfigEntry<float> LightDistanceScale;

		public static ConfigEntry<float> ShadowDistanceScale;

		public static ConfigEntry<bool> RenderingEnabled;

		public static ConfigEntry<int> ParticleRaycastBudget;

		public static ConfigEntry<float> MaxShadowDistance;

		public static ConfigEntry<float> SmallObjectDistance;

		public static ConfigEntry<bool> GpuEnabled;

		public static ConfigEntry<float> ReflectionInterval;

		public static ConfigEntry<int> ReflectionResolution;

		public static ConfigEntry<int> TextureMipmapLimit;

		public static ConfigEntry<float> LodBiasScale;

		public static ConfigEntry<int> ShadowCascades;

		public static ConfigEntry<float> GrassDistanceScale;

		public static ConfigEntry<float> GrassDensityScale;

		public static ConfigEntry<bool> CharactersEnabled;

		public static ConfigEntry<bool> VisualOncePerFrame;

		public static ConfigEntry<float> TiltMaxDistance;

		public static ConfigEntry<float> HitchThresholdMs;

		public static ConfigEntry<bool> BuildingsEnabled;

		public static ConfigEntry<bool> SnowFixFps;

		public static ConfigEntry<float> SnowReferenceFps;

		public static ConfigEntry<bool> SnowQuantizeWrites;

		public static ConfigEntry<bool> SpreadCoverPass;

		public static ConfigEntry<int> CoverPassPiecesPerFrame;

		public static ConfigEntry<bool> MemoryEnabled;

		public static ConfigEntry<bool> CleanupOnTeleport;

		public static ConfigEntry<bool> CleanupOnRespawn;

		public static ConfigEntry<bool> DeferHourlyCleanup;

		public static ConfigEntry<float> DeferMaxMinutes;

		public static ConfigEntry<float> GcTimeSliceMs;

		public static void Bind(ConfigFile c)
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Expected O, but got Unknown
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Expected O, but got Unknown
			//IL_0173: Unknown result type (might be due to invalid IL or missing references)
			//IL_017d: Expected O, but got Unknown
			//IL_01ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b5: Expected O, but got Unknown
			//IL_01f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0201: Expected O, but got Unknown
			//IL_022f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0239: Expected O, but got Unknown
			//IL_0267: Unknown result type (might be due to invalid IL or missing references)
			//IL_0271: Expected O, but got Unknown
			//IL_02ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c4: Expected O, but got Unknown
			//IL_02f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0300: Expected O, but got Unknown
			//IL_0322: Unknown result type (might be due to invalid IL or missing references)
			//IL_032c: Expected O, but got Unknown
			//IL_035a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0364: Expected O, but got Unknown
			//IL_0395: Unknown result type (might be due to invalid IL or missing references)
			//IL_039f: Expected O, but got Unknown
			//IL_03cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d7: Expected O, but got Unknown
			//IL_0405: Unknown result type (might be due to invalid IL or missing references)
			//IL_040f: Expected O, but got Unknown
			//IL_0473: Unknown result type (might be due to invalid IL or missing references)
			//IL_047d: Expected O, but got Unknown
			//IL_04e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_04eb: Expected O, but got Unknown
			//IL_054c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0556: Expected O, but got Unknown
			//IL_05f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_05fa: Expected O, but got Unknown
			//IL_0628: Unknown result type (might be due to invalid IL or missing references)
			//IL_0632: Expected O, but got Unknown
			ProfilerEnabled = c.Bind<bool>("1 - Profiler", "Enabled", false, "Log average/max time per game subsystem (MonoUpdaters, WearNTear, lights...) to the BepInEx log. Use it to find what is actually slow in YOUR world. Costs a little CPU, keep off for normal play. Needs a game restart to toggle.");
			ProfilerInterval = c.Bind<float>("1 - Profiler", "ReportIntervalSeconds", 15f, new ConfigDescription("How often the profiler writes its report.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 120f), Array.Empty<object>()));
			HitchThresholdMs = c.Bind<float>("1 - Profiler", "HitchThresholdMs", 100f, new ConfigDescription("Log every frame slower than this (a freeze) with what took the time. 0 = off. Only works while the profiler is enabled.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2000f), Array.Empty<object>()));
			WeatherEnabled = c.Bind<bool>("2 - Weather", "Enabled", true, "Tune rain/snow/ash/mist particle systems. Client-side only, purely visual. Needs a game restart to toggle.");
			WeatherDensity = c.Bind<float>("2 - Weather", "ParticleDensity", 0.6f, new ConfigDescription("Multiplier for weather particle emission and max particles (1 = vanilla).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 1f), Array.Empty<object>()));
			WeatherNoCollision = c.Bind<bool>("2 - Weather", "DisableParticleCollision", true, "Turn off world collision on weather particles (raindrops hitting things). Usually the most expensive part of storms.");
			WeatherNoLights = c.Bind<bool>("2 - Weather", "DisableParticleLights", true, "Turn off per-particle lights on weather particles.");
			WeatherNoTrails = c.Bind<bool>("2 - Weather", "DisableParticleTrails", false, "Turn off particle trails on weather particles.");
			LightsEnabled = c.Bind<bool>("3 - Lights", "Enabled", false, "Scale the game's own light LOD distances (LightLod). Client-side only. Needs a game restart to toggle.");
			LightDistanceScale = c.Bind<float>("3 - Lights", "LightDistanceScale", 0.85f, new ConfigDescription("Multiplier for the distance at which lights switch off (1 = vanilla). Applies to lights created after the change.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.2f, 1.5f), Array.Empty<object>()));
			ShadowDistanceScale = c.Bind<float>("3 - Lights", "ShadowDistanceScale", 0.7f, new ConfigDescription("Multiplier for the distance at which light shadows switch off (1 = vanilla). Light shadows are expensive in bases with many torches. Applies to lights created after the change.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 1.5f), Array.Empty<object>()));
			RenderingEnabled = c.Bind<bool>("4 - Rendering", "Enabled", true, "Apply extra Unity quality/culling tweaks after the game applies its own graphics settings. Needs a game restart to toggle.");
			ParticleRaycastBudget = c.Bind<int>("4 - Rendering", "ParticleRaycastBudget", 64, new ConfigDescription("Max raycasts per frame for particle collisions (Unity default 256). 0 = leave untouched.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 4096), Array.Empty<object>()));
			MaxShadowDistance = c.Bind<float>("4 - Rendering", "MaxShadowDistance", 0f, new ConfigDescription("Cap on directional shadow distance in metres. 0 = leave the game's value untouched.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 300f), Array.Empty<object>()));
			SmallObjectDistance = c.Bind<float>("4 - Rendering", "SmallObjectCullDistance", 0f, new ConfigDescription("Stop drawing objects on the Default_small layer (small clutter) beyond this many metres. 0 = off. Rendering only, no gameplay effect.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 300f), Array.Empty<object>()));
			GpuEnabled = c.Bind<bool>("5 - Low-end GPU", "Enabled", true, "Settings that go below the lowest in-game graphics preset. Meant for weak/laptop GPUs. Needs a game restart to toggle.");
			ReflectionInterval = c.Bind<float>("5 - Low-end GPU", "ReflectionUpdateSeconds", 10f, new ConfigDescription("How often the world reflection probe is re-rendered (vanilla 3). Each update renders the scene 6 times, which causes stutter on weak GPUs.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(3f, 60f), Array.Empty<object>()));
			ReflectionResolution = c.Bind<int>("5 - Low-end GPU", "ReflectionResolution", 64, new ConfigDescription("Resolution of the world reflection probe. Lower = cheaper, blurrier reflections on water/metal. 0 = leave untouched.", (AcceptableValueBase)(object)new AcceptableValueList<int>(new int[6] { 0, 16, 32, 64, 128, 256 }), Array.Empty<object>()));
			TextureMipmapLimit = c.Bind<int>("5 - Low-end GPU", "TextureQualityReduction", 0, new ConfigDescription("0 = full textures, 1 = half resolution, 2 = quarter. 1 is a big VRAM saver on 2 GB cards.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 3), Array.Empty<object>()));
			LodBiasScale = c.Bind<float>("5 - Low-end GPU", "LodBiasScale", 1f, new ConfigDescription("Multiplier for the game's LOD bias. Below 1 switches objects to simpler models sooner (vanilla lowest setting = 1.0 bias).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.3f, 1f), Array.Empty<object>()));
			ShadowCascades = c.Bind<int>("5 - Low-end GPU", "ShadowCascades", 0, new ConfigDescription("Number of sun shadow cascades. 1 is cheapest. 0 = leave the game's value untouched.", (AcceptableValueBase)(object)new AcceptableValueList<int>(new int[4] { 0, 1, 2, 4 }), Array.Empty<object>()));
			GrassDistanceScale = c.Bind<float>("5 - Low-end GPU", "GrassDistanceScale", 1f, new ConfigDescription("Multiplier for how far grass is drawn. Needs a game restart.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.3f, 1f), Array.Empty<object>()));
			GrassDensityScale = c.Bind<float>("5 - Low-end GPU", "GrassDensityScale", 1f, new ConfigDescription("Multiplier for grass density. Needs a game restart.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.2f, 1f), Array.Empty<object>()));
			CharactersEnabled = c.Bind<bool>("6 - Characters", "Enabled", true, "Reduce per-creature CPU work without changing AI, combat or movement. Needs a game restart to toggle.");
			VisualOncePerFrame = c.Bind<bool>("6 - Characters", "VisualUpdatesOncePerFrame", true, "Run purely visual creature updates (slope tilt, water/slide effects) once per frame instead of every physics step. Big win at low FPS, where there are 3-5 physics steps per frame.");
			TiltMaxDistance = c.Bind<float>("6 - Characters", "SlopeTiltMaxDistance", 60f, new ConfigDescription("Creatures farther than this (metres) skip slope-tilt updates. 0 = never skip.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 300f), Array.Empty<object>()));
			BuildingsEnabled = c.Bind<bool>("7 - Buildings", "Enabled", true, "Fixes for building pieces (snow, per-second update spike). Gameplay-equivalent to vanilla. Needs a game restart to toggle.");
			SnowFixFps = c.Bind<bool>("7 - Buildings", "FixSnowFpsDependency", true, "Vanilla snow on buildings grows faster at low FPS (bug). When on, it grows at the same speed as vanilla at ReferenceFps, for everyone.");
			SnowReferenceFps = c.Bind<float>("7 - Buildings", "SnowReferenceFps", 60f, new ConfigDescription("FPS at which vanilla snow speed is matched.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(20f, 240f), Array.Empty<object>()));
			SnowQuantizeWrites = c.Bind<bool>("7 - Buildings", "ReduceSnowNetworkWrites", true, "Only write the snow level to the network when it changed by a visible amount (2%) instead of every second for every piece. Less CPU and network traffic in snowstorms.");
			SpreadCoverPass = c.Bind<bool>("7 - Buildings", "SpreadPerSecondPass", true, "Vanilla updates roof/ash state of ALL pieces in one frame once per second (a spike on big bases). Spread it over several frames instead.");
			CoverPassPiecesPerFrame = c.Bind<int>("7 - Buildings", "PiecesPerFrame", 1000, new ConfigDescription("How many pieces the spread pass handles per frame.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(100, 10000), Array.Empty<object>()));
			MemoryEnabled = c.Bind<bool>("8 - Memory", "Enabled", true, "Run 'unload unused assets' only while the screen is black, instead of at random moments. Needs a game restart to toggle.");
			CleanupOnTeleport = c.Bind<bool>("8 - Memory", "OnTeleport", true, "Clean up during long-distance teleports (portals, dev commands). The loading screen stays a moment longer instead of freezing later.");
			CleanupOnRespawn = c.Bind<bool>("8 - Memory", "OnRespawn", true, "Run vanilla's respawn cleanup (same 20-minute rule) right when respawn starts, while the screen is black, instead of after the player appears.");
			DeferHourlyCleanup = c.Bind<bool>("8 - Memory", "DeferHourlyCleanup", true, "Vanilla also cleans up once per hour at any moment (a freeze, even in combat). Postpone it to the next teleport/sleep/respawn.");
			DeferMaxMinutes = c.Bind<float>("8 - Memory", "DeferMaxMinutes", 20f, new ConfigDescription("If no teleport/sleep/respawn happens within this time, do the postponed cleanup anyway.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 120f), Array.Empty<object>()));
			GcTimeSliceMs = c.Bind<float>("8 - Memory", "IncrementalGcTimeSliceMs", 0f, new ConfigDescription("Only if the game build uses incremental GC (see 'GC mode' line in the log at startup): max milliseconds per frame the garbage collector may work, so one long pause becomes many short ones. 0 = leave the game's value untouched. Does not change gameplay. Setting back to 0 restores the original value.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
		}
	}
}
namespace LeanHeim.Patches
{
	internal static class BuildingStats
	{
		private sealed class Counts
		{
			public int Pieces;

			public int Renderers;

			public int Active;

			public int Visible;

			public int ShadowCasters;

			public int VisibleShadowCasters;

			public int WithPropertyBlock;

			public int InstancingMaterials;

			public int Lods;

			public readonly HashSet<Material> Materials = new HashSet<Material>();

			public readonly HashSet<Mesh> Meshes = new HashSet<Mesh>();
		}

		private static readonly FieldRef<WearNTear, List<Renderer>> RenderersRef = AccessTools.FieldRefAccess<WearNTear, List<Renderer>>("m_renderers");

		private const int PiecesPerFrame = 300;

		private static Counts _scan = new Counts();

		private static int _index;

		private static string _lastReport;

		internal static void Tick()
		{
			List<WearNTear> allInstances = WearNTear.GetAllInstances();
			int num = Mathf.Min(_index + 300, allInstances.Count);
			for (int i = _index; i < num; i++)
			{
				Scan(allInstances[i]);
			}
			_index = num;
			if (_index >= allInstances.Count)
			{
				if (_scan.Pieces > 0)
				{
					_lastReport = Format(_scan);
				}
				_scan = new Counts();
				_index = 0;
			}
		}

		private static void Scan(WearNTear w)
		{
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Invalid comparison between Unknown and I4
			if ((Object)(object)w == (Object)null)
			{
				return;
			}
			_scan.Pieces++;
			if ((Object)(object)((Component)w).GetComponent<LODGroup>() != (Object)null)
			{
				_scan.Lods++;
			}
			List<Renderer> list = RenderersRef.Invoke(w);
			if (list == null)
			{
				return;
			}
			MeshFilter val = default(MeshFilter);
			foreach (Renderer item in list)
			{
				if ((Object)(object)item == (Object)null)
				{
					continue;
				}
				_scan.Renderers++;
				if (!item.enabled || !((Component)item).gameObject.activeInHierarchy)
				{
					continue;
				}
				_scan.Active++;
				bool flag = (int)item.shadowCastingMode > 0;
				if (item.isVisible)
				{
					_scan.Visible++;
					if (flag)
					{
						_scan.VisibleShadowCasters++;
					}
				}
				if (flag)
				{
					_scan.ShadowCasters++;
				}
				if (item.HasPropertyBlock())
				{
					_scan.WithPropertyBlock++;
				}
				Material sharedMaterial = item.sharedMaterial;
				if ((Object)(object)sharedMaterial != (Object)null)
				{
					_scan.Materials.Add(sharedMaterial);
					if (sharedMaterial.enableInstancing)
					{
						_scan.InstancingMaterials++;
					}
				}
				if (item is MeshRenderer && ((Component)item).TryGetComponent<MeshFilter>(ref val) && (Object)(object)val.sharedMesh != (Object)null)
				{
					_scan.Meshes.Add(val.sharedMesh);
				}
			}
		}

		private static string Format(Counts c)
		{
			return $"  Buildings: pieces {c.Pieces} (with LODGroup {c.Lods}), renderers {c.Renderers}, active {c.Active}, " + $"visible now {c.Visible} (shadow casters among them {c.VisibleShadowCasters}), shadow casters total {c.ShadowCasters}, " + $"with property block {c.WithPropertyBlock}, instancing-enabled {c.InstancingMaterials}/{c.Active}, " + $"unique materials {c.Materials.Count}, unique meshes {c.Meshes.Count}";
		}

		internal static string Report()
		{
			return _lastReport;
		}
	}
	[HarmonyPatch(typeof(Character))]
	internal static class CharacterPatches
	{
		private sealed class State
		{
			public int TiltFrame = -1;

			public float PendingTiltDt;

			public int FxFrame = -1;
		}

		private const float MaxPendingDt = 0.25f;

		private static readonly ConditionalWeakTable<Character, State> States = new ConditionalWeakTable<Character, State>();

		private static Vector3 _camPos;

		private static int _camFrame = -1;

		private static Vector3 CameraPos()
		{
			//IL_003a: 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_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			int frameCount = Time.frameCount;
			if (_camFrame != frameCount)
			{
				_camFrame = frameCount;
				Camera mainCamera = Utils.GetMainCamera();
				_camPos = (((Object)(object)mainCamera != (Object)null) ? ((Component)mainCamera).transform.position : Vector3.zero);
			}
			return _camPos;
		}

		[HarmonyPatch("UpdateGroundTilt")]
		[HarmonyPrefix]
		private static bool UpdateGroundTilt_Prefix(Character __instance, ref float dt)
		{
			//IL_0035: 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_003f: 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)
			if (__instance is Player)
			{
				return true;
			}
			State orCreateValue = States.GetOrCreateValue(__instance);
			int frameCount = Time.frameCount;
			float value = Settings.TiltMaxDistance.Value;
			if (value > 0f)
			{
				Vector3 val = ((Component)__instance).transform.position - CameraPos();
				if (((Vector3)(ref val)).sqrMagnitude > value * value)
				{
					goto IL_006b;
				}
			}
			if (!Settings.VisualOncePerFrame.Value || orCreateValue.TiltFrame != frameCount)
			{
				dt = Mathf.Min(dt + orCreateValue.PendingTiltDt, 0.25f);
				orCreateValue.PendingTiltDt = 0f;
				orCreateValue.TiltFrame = frameCount;
				return true;
			}
			goto IL_006b;
			IL_006b:
			orCreateValue.PendingTiltDt = Mathf.Min(orCreateValue.PendingTiltDt + dt, 0.25f);
			return false;
		}

		[HarmonyPatch("UpdateContinousEffects")]
		[HarmonyPrefix]
		private static bool UpdateContinousEffects_Prefix(Character __instance)
		{
			if (!Settings.VisualOncePerFrame.Value || __instance is Player)
			{
				return true;
			}
			State orCreateValue = States.GetOrCreateValue(__instance);
			int frameCount = Time.frameCount;
			if (orCreateValue.FxFrame == frameCount)
			{
				return false;
			}
			orCreateValue.FxFrame = frameCount;
			return true;
		}
	}
	[HarmonyPatch]
	internal static class GpuPatches
	{
		[HarmonyPatch(typeof(ReflectionUpdate), "Start")]
		[HarmonyPostfix]
		private static void ReflectionUpdate_Start_Postfix(ReflectionUpdate __instance)
		{
			ApplyReflection(__instance);
		}

		[HarmonyPatch(typeof(ClutterSystem), "Awake")]
		[HarmonyPostfix]
		private static void ClutterSystem_Awake_Postfix(ClutterSystem __instance)
		{
			__instance.m_distance *= Settings.GrassDistanceScale.Value;
			__instance.m_amountScale *= Settings.GrassDensityScale.Value;
		}

		[HarmonyPatch(typeof(GraphicsSettingsManager), "ApplyQualitySettings")]
		[HarmonyPostfix]
		private static void ApplyQualitySettings_Postfix()
		{
			ApplyQuality();
		}

		internal static void ApplyReflection(ReflectionUpdate r)
		{
			if (!((Object)(object)r == (Object)null))
			{
				r.m_interval = Settings.ReflectionInterval.Value;
				r.m_transitionDuration = Mathf.Min(r.m_transitionDuration, r.m_interval);
				SetupProbe(r.m_probe1);
				SetupProbe(r.m_probe2);
			}
		}

		private static void SetupProbe(ReflectionProbe probe)
		{
			if (!((Object)(object)probe == (Object)null))
			{
				int value = Settings.ReflectionResolution.Value;
				if (value > 0)
				{
					probe.resolution = value;
				}
				probe.timeSlicingMode = (ReflectionProbeTimeSlicingMode)1;
			}
		}

		internal static void ApplyQuality()
		{
			QualitySettings.globalTextureMipmapLimit = Settings.TextureMipmapLimit.Value;
			float value = Settings.LodBiasScale.Value;
			if (value < 1f)
			{
				QualitySettings.lodBias *= value;
			}
			int value2 = Settings.ShadowCascades.Value;
			if (value2 > 0)
			{
				QualitySettings.shadowCascades = value2;
			}
		}

		internal static void Reapply()
		{
			GraphicsSettingsManager instance = GraphicsSettingsManager.Instance;
			if ((Object)(object)instance != (Object)null)
			{
				AccessTools.Method(typeof(GraphicsSettingsManager), "ApplyGraphicsSettingsToCurrentSession", (Type[])null, (Type[])null)?.Invoke(instance, null);
			}
			else
			{
				ApplyQuality();
			}
			ApplyReflection(ReflectionUpdate.instance);
		}
	}
	[HarmonyPatch(typeof(LightLod))]
	internal static class LightPatches
	{
		[HarmonyPatch("Awake")]
		[HarmonyPostfix]
		private static void Awake_Postfix(LightLod __instance)
		{
			if (__instance.m_lightLod)
			{
				__instance.m_lightDistance *= Settings.LightDistanceScale.Value;
			}
			if (__instance.m_shadowLod)
			{
				__instance.m_shadowDistance *= Settings.ShadowDistanceScale.Value;
			}
		}
	}
	[HarmonyPatch]
	internal static class MemoryPatches
	{
		private static readonly TimeSpan VanillaCooldown = TimeSpan.FromSeconds(1200.0);

		private static readonly FieldRef<Game, DateTime> LastCollect = AccessTools.FieldRefAccess<Game, DateTime>("m_lastCollectResources");

		private static readonly FieldRef<Player, bool> Teleporting = AccessTools.FieldRefAccess<Player, bool>("m_teleporting");

		private static readonly FieldRef<Player, bool> DistantTeleport = AccessTools.FieldRefAccess<Player, bool>("m_distantTeleport");

		private static readonly FieldRef<Player, float> TeleportTimer = AccessTools.FieldRefAccess<Player, float>("m_teleportTimer");

		private static readonly FieldRef<Player, Vector3> TeleportTargetPos = AccessTools.FieldRefAccess<Player, Vector3>("m_teleportTargetPos");

		private static readonly FieldRef<Player, Quaternion> TeleportTargetRot = AccessTools.FieldRefAccess<Player, Quaternion>("m_teleportTargetRot");

		private static readonly FieldRef<Character, Rigidbody> Body = AccessTools.FieldRefAccess<Character, Rigidbody>("m_body");

		private static AsyncOperation _op;

		private static float _opStarted;

		private static bool _teleportHandled;

		private static bool _hourlyPending;

		private static float _hourlyPendingSince;

		private static DateTime _hourlyPendingSinceDate;

		private const float MaxHoldSeconds = 10f;

		private static ulong _originalSliceNs;

		private static bool _originalKnown;

		private static void StartCleanup(string reason)
		{
			Game instance = Game.instance;
			if (!((Object)(object)instance == (Object)null) && _op == null)
			{
				Plugin.Log.LogInfo((object)("Unloading unused assets (" + reason + ")"));
				_op = Resources.UnloadUnusedAssets();
				_opStarted = Time.realtimeSinceStartup;
				LastCollect.Invoke(instance) = DateTime.Now;
				_hourlyPending = false;
			}
		}

		private static bool CleanupRunning()
		{
			if (_op == null)
			{
				return false;
			}
			if (_op.isDone || Time.realtimeSinceStartup - _opStarted > 10f)
			{
				_op = null;
				return false;
			}
			return true;
		}

		[HarmonyPatch(typeof(Player), "UpdateTeleport")]
		[HarmonyPrefix]
		private static bool UpdateTeleport_Prefix(Player __instance)
		{
			//IL_0053: 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_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			if (!Settings.CleanupOnTeleport.Value || (Object)(object)__instance != (Object)(object)Player.m_localPlayer)
			{
				return true;
			}
			if (!Teleporting.Invoke(__instance))
			{
				_teleportHandled = false;
				return true;
			}
			if (!DistantTeleport.Invoke(__instance) || _teleportHandled)
			{
				return true;
			}
			Vector3 val = TeleportTargetPos.Invoke(__instance);
			if (TeleportTimer.Invoke(__instance) <= 8f || (Object)(object)ZNetScene.instance == (Object)null || !ZNetScene.instance.IsAreaReady(val))
			{
				return true;
			}
			if (_op == null)
			{
				StartCleanup("teleport");
			}
			if (CleanupRunning())
			{
				((Component)__instance).transform.position = val;
				((Component)__instance).transform.rotation = TeleportTargetRot.Invoke(__instance);
				Rigidbody val2 = Body.Invoke((Character)(object)__instance);
				if ((Object)(object)val2 != (Object)null)
				{
					val2.linearVelocity = Vector3.zero;
				}
				return false;
			}
			_teleportHandled = true;
			return true;
		}

		[HarmonyPatch(typeof(Game), "_RequestRespawn")]
		[HarmonyPostfix]
		private static void RequestRespawn_Postfix()
		{
			Game instance = Game.instance;
			if (Settings.CleanupOnRespawn.Value && !((Object)(object)instance == (Object)null) && (_hourlyPending || DateTime.Now - LastCollect.Invoke(instance) > VanillaCooldown))
			{
				StartCleanup("respawn");
			}
		}

		[HarmonyPatch(typeof(Game), "CollectResourcesCheckPeriodic")]
		[HarmonyPrefix]
		private static bool CollectResourcesCheckPeriodic_Prefix()
		{
			if (!Settings.DeferHourlyCleanup.Value)
			{
				return true;
			}
			if (!_hourlyPending)
			{
				_hourlyPending = true;
				_hourlyPendingSince = Time.realtimeSinceStartup;
				_hourlyPendingSinceDate = DateTime.Now;
			}
			return false;
		}

		internal static void ApplyGc()
		{
			//IL_0006: 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_003e: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				bool isIncremental = GarbageCollector.isIncremental;
				Mode gCMode = GarbageCollector.GCMode;
				ulong num = (isIncremental ? GarbageCollector.incrementalTimeSliceNanoseconds : 0);
				if (!_originalKnown)
				{
					_originalSliceNs = num;
					_originalKnown = true;
					Plugin.Log.LogInfo((object)$"GC mode: {gCMode}, incremental {isIncremental}, time slice {(double)num / 1000000.0:0.##} ms, mono heap {GC.GetTotalMemory(forceFullCollection: false) / 1048576} MB");
				}
				float value = Settings.GcTimeSliceMs.Value;
				if (!isIncremental)
				{
					if (value > 0f)
					{
						Plugin.Log.LogWarning((object)"IncrementalGcTimeSliceMs is set but this build does not use incremental GC - ignored.");
					}
					return;
				}
				ulong num2 = ((value > 0f) ? ((ulong)(value * 1000000f)) : _originalSliceNs);
				if (num2 != num)
				{
					GarbageCollector.incrementalTimeSliceNanoseconds = num2;
					Plugin.Log.LogInfo((object)($"GC time slice {(double)num / 1000000.0:0.##} ms -> {(double)num2 / 1000000.0:0.##} ms" + ((value > 0f) ? "" : " (restored original)")));
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("GC settings not available: " + ex.Message));
			}
		}

		internal static void Tick()
		{
			CleanupRunning();
			if (!_hourlyPending)
			{
				return;
			}
			Game instance = Game.instance;
			if ((Object)(object)instance != (Object)null && LastCollect.Invoke(instance) >= _hourlyPendingSinceDate)
			{
				_hourlyPending = false;
			}
			else if (Time.realtimeSinceStartup - _hourlyPendingSince > Settings.DeferMaxMinutes.Value * 60f)
			{
				_hourlyPending = false;
				if (instance != null)
				{
					instance.CollectResources(true);
				}
			}
		}
	}
	internal static class ProfilerPatches
	{
		private sealed class Stat
		{
			public long Ticks;

			public long MaxTicks;

			public int Calls;
		}

		private sealed class Marker
		{
			public string Name;

			public ProfilerRecorder Recorder;

			public double TotalNs;
		}

		[HarmonyPatch]
		private static class MonoUpdaterScopes
		{
			private static IEnumerable<MethodBase> TargetMethods()
			{
				string[] array = new string[4] { "CustomUpdate", "CustomFixedUpdate", "CustomLateUpdate", "UpdateAI" };
				foreach (string text in array)
				{
					MethodInfo methodInfo = AccessTools.Method(typeof(MonoUpdatersExtra), text, (Type[])null, (Type[])null);
					if (methodInfo != null)
					{
						yield return methodInfo;
					}
				}
			}

			private static void Prefix(out long __state)
			{
				__state = Stopwatch.GetTimestamp();
			}

			private static void Postfix(string profileScope, long __state)
			{
				Record(profileScope, __state);
			}
		}

		[HarmonyPatch]
		private static class NamedScopes
		{
			private static readonly string[] Targets = new string[27]
			{
				"WearNTearUpdater:Update", "LightLod:UpdateLights", "EnvMan:Update", "ZNetScene:Update", "ZDOMan:Update", "ZoneSystem:Update", "Player:Update", "Player:FixedUpdate", "Hud:Update", "GameCamera:LateUpdate",
				"Piece:Awake", "ZNetView:Awake", "WearNTear:UpdateWear", "WearNTear:UpdateCover", "WearNTear:UpdateSupport", "WearNTear:UpdateAshlandsMaterialValues", "Character:CalculateLiquidDepth", "Character:UpdateContinousEffects", "Character:UpdateWater", "Character:UpdateGroundTilt",
				"Character:SetVisible", "Character:UpdateGroundContact", "Character:UpdateMotion", "Character:UpdateNoise", "Character:UpdatePheromones", "Character:UpdateHeatEffects", "SEMan:Update"
			};

			private static readonly Dictionary<MethodBase, string> Names = new Dictionary<MethodBase, string>();

			private static IEnumerable<MethodBase> TargetMethods()
			{
				string[] targets = Targets;
				foreach (string text in targets)
				{
					string[] array = text.Split(new char[1] { ':' });
					Type type = AccessTools.TypeByName(array[0]);
					MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, array[1], (Type[])null, (Type[])null));
					if (methodInfo == null)
					{
						Plugin.Log.LogDebug((object)("Profiler: skipping missing target " + text));
					}
					else
					{
						yield return methodInfo;
					}
				}
			}

			private static void Prefix(out long __state)
			{
				__state = Stopwatch.GetTimestamp();
			}

			private static void Postfix(MethodBase __originalMethod, long __state)
			{
				if (!Names.TryGetValue(__originalMethod, out var value))
				{
					value = (Names[__originalMethod] = __originalMethod.DeclaringType.Name + "." + __originalMethod.Name);
				}
				Record(value, __state);
			}
		}

		private static readonly Dictionary<string, Stat> Stats = new Dictionary<string, Stat>();

		private static readonly double TickToMs = 1000.0 / (double)Stopwatch.Frequency;

		private static float _windowStart;

		private static int _windowFrames;

		private static int _windowStartGc;

		private static readonly Dictionary<string, Stat> FrameStats = new Dictionary<string, Stat>();

		private static long _lastFrameTs;

		private static int _lastGcCount;

		private static int _hitchesThisWindow;

		private static int _hitchesSuppressed;

		private const int MaxHitchLogsPerWindow = 15;

		private static readonly FrameTiming[] Timings = (FrameTiming[])(object)new FrameTiming[1];

		private static double _cpuMain;

		private static double _cpuRender;

		private static double _gpu;

		private static int _timingSamples;

		private static Marker[] _markers;

		private static readonly (ProfilerCategory cat, string name)[] MarkerNames = new(ProfilerCategory, string)[13]
		{
			(ProfilerCategory.Internal, "Main Thread"),
			(ProfilerCategory.Internal, "PlayerLoop"),
			(ProfilerCategory.Render, "Gfx.WaitForPresentOnGfxThread"),
			(ProfilerCategory.Render, "Gfx.WaitForRenderThread"),
			(ProfilerCategory.Render, "Camera.Render"),
			(ProfilerCategory.Render, "Shadows.RenderShadowMap"),
			(ProfilerCategory.Physics, "Physics.Simulate"),
			(ProfilerCategory.Animation, "Animators.Update"),
			(ProfilerCategory.Animation, "Animators.WriteJob"),
			(ProfilerCategory.Scripts, "BehaviourUpdate"),
			(ProfilerCategory.Scripts, "FixedBehaviourUpdate"),
			(ProfilerCategory.Scripts, "LateBehaviourUpdate"),
			(ProfilerCategory.Internal, "GC.Collect")
		};

		internal static void Install(Harmony harmony)
		{
			harmony.CreateClassProcessor(typeof(MonoUpdaterScopes)).Patch();
			harmony.CreateClassProcessor(typeof(NamedScopes)).Patch();
			Plugin.Log.LogInfo((object)"Enabled module ProfilerPatches");
		}

		internal static void Record(string key, long startTimestamp)
		{
			long num = Stopwatch.GetTimestamp() - startTimestamp;
			if (!Stats.TryGetValue(key, out var value))
			{
				value = (Stats[key] = new Stat());
			}
			value.Ticks += num;
			value.Calls++;
			if (num > value.MaxTicks)
			{
				value.MaxTicks = num;
			}
			if (!FrameStats.TryGetValue(key, out var value2))
			{
				value2 = (FrameStats[key] = new Stat());
			}
			value2.Ticks += num;
			value2.Calls++;
		}

		private static void CheckHitch()
		{
			long timestamp = Stopwatch.GetTimestamp();
			int num = GC.CollectionCount(0);
			double num2 = ((_lastFrameTs == 0L) ? 0.0 : ((double)(timestamp - _lastFrameTs) * TickToMs));
			int gcDelta = num - _lastGcCount;
			_lastFrameTs = timestamp;
			_lastGcCount = num;
			float value = Settings.HitchThresholdMs.Value;
			if (value > 0f && num2 >= (double)value && (Object)(object)Player.m_localPlayer != (Object)null)
			{
				if (_hitchesThisWindow++ < 15)
				{
					LogHitch(num2, gcDelta);
				}
				else
				{
					_hitchesSuppressed++;
				}
			}
			foreach (Stat value2 in FrameStats.Values)
			{
				value2.Ticks = 0L;
				value2.Calls = 0;
			}
		}

		private static void LogHitch(double frameMs, int gcDelta)
		{
			List<KeyValuePair<string, Stat>> list = new List<KeyValuePair<string, Stat>>();
			foreach (KeyValuePair<string, Stat> frameStat in FrameStats)
			{
				if (frameStat.Value.Calls > 0)
				{
					list.Add(frameStat);
				}
			}
			list.Sort((KeyValuePair<string, Stat> a, KeyValuePair<string, Stat> b) => b.Value.Ticks.CompareTo(a.Value.Ticks));
			FrameStats.TryGetValue("MonoUpdaters.FixedUpdate.Character", out var value);
			string text = ((!((Object)(object)EnvMan.instance != (Object)null)) ? "?" : EnvMan.instance.GetCurrentEnvironment()?.m_name);
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append(string.Format("[LeanHeim hitch] {0:0} ms frame | physics steps {1} | GC {2} | heap {3} MB | env {4} | chars {5} | smoke {6} | top:", frameMs, value?.Calls ?? 0, (gcDelta > 0) ? ("YES x" + gcDelta) : "no", GC.GetTotalMemory(forceFullCollection: false) / 1048576, text, Character.GetAllCharacters().Count, Smoke.GetTotalSmoke()));
			int num = 0;
			foreach (KeyValuePair<string, Stat> item in list)
			{
				if (num++ >= 6)
				{
					break;
				}
				stringBuilder.Append($" {item.Key}={(double)item.Value.Ticks * TickToMs:0.0}ms");
				if (item.Value.Calls > 1)
				{
					stringBuilder.Append($"(x{item.Value.Calls})");
				}
			}
			Plugin.Log.LogWarning((object)stringBuilder.ToString());
		}

		private static void SampleFrameTimings()
		{
			try
			{
				FrameTimingManager.CaptureFrameTimings();
				if (FrameTimingManager.GetLatestTimings(1u, Timings) != 0)
				{
					_cpuMain += Timings[0].cpuMainThreadFrameTime;
					_cpuRender += Timings[0].cpuRenderThreadFrameTime;
					_gpu += Timings[0].gpuFrameTime;
					_timingSamples++;
				}
			}
			catch
			{
			}
		}

		private static void SampleMarkers()
		{
			//IL_0044: 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_0061: Unknown result type (might be due to invalid IL or missing references)
			if (_markers == null)
			{
				_markers = new Marker[MarkerNames.Length];
				for (int i = 0; i < MarkerNames.Length; i++)
				{
					Marker marker = new Marker
					{
						Name = MarkerNames[i].name
					};
					try
					{
						marker.Recorder = ProfilerRecorder.StartNew(MarkerNames[i].cat, MarkerNames[i].name, 1, (ProfilerRecorderOptions)24);
					}
					catch
					{
					}
					_markers[i] = marker;
				}
			}
			Marker[] markers = _markers;
			foreach (Marker marker2 in markers)
			{
				if (((ProfilerRecorder)(ref marker2.Recorder)).Valid)
				{
					marker2.TotalNs += ((ProfilerRecorder)(ref marker2.Recorder)).LastValue;
				}
			}
		}

		private static void AppendMarkers(StringBuilder sb, int frames)
		{
			if (_markers == null)
			{
				return;
			}
			sb.Append("  Unity markers (ms/frame):");
			Marker[] markers = _markers;
			foreach (Marker marker in markers)
			{
				if (((ProfilerRecorder)(ref marker.Recorder)).Valid)
				{
					sb.Append($" {marker.Name}={marker.TotalNs / 1000000.0 / (double)Math.Max(1, frames):0.0}");
				}
				else
				{
					sb.Append(" " + marker.Name + "=n/a");
				}
				marker.TotalNs = 0.0;
			}
			sb.AppendLine();
		}

		internal static void Tick()
		{
			_windowFrames++;
			CheckHitch();
			BuildingStats.Tick();
			SampleFrameTimings();
			SampleMarkers();
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (_windowStart == 0f)
			{
				_windowStart = realtimeSinceStartup;
				return;
			}
			float num = realtimeSinceStartup - _windowStart;
			if (num < Settings.ProfilerInterval.Value)
			{
				return;
			}
			List<KeyValuePair<string, Stat>> list = new List<KeyValuePair<string, Stat>>(Stats);
			list.Sort((KeyValuePair<string, Stat> a, KeyValuePair<string, Stat> b) => b.Value.Ticks.CompareTo(a.Value.Ticks));
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine($"[LeanHeim profiler] {num:0.#}s, {_windowFrames} frames, avg {1000f * num / (float)Math.Max(1, _windowFrames):0.0} ms/frame ({(float)_windowFrames / num:0} fps)");
			if (_timingSamples > 0)
			{
				stringBuilder.AppendLine($"  Unity frame timings (avg): cpuMain {_cpuMain / (double)_timingSamples:0.0} ms, cpuRender {_cpuRender / (double)_timingSamples:0.0} ms, gpu {_gpu / (double)_timingSamples:0.0} ms");
			}
			else
			{
				stringBuilder.AppendLine("  Unity frame timings: not available");
			}
			stringBuilder.AppendLine($"  GC: gen0 collections {GC.CollectionCount(0) - _windowStartGc}, mono heap {GC.GetTotalMemory(forceFullCollection: false) / 1048576} MB");
			_windowStartGc = GC.CollectionCount(0);
			AppendMarkers(stringBuilder, _windowFrames);
			stringBuilder.AppendLine($"  Objects: characters {Character.GetAllCharacters().Count}, wearNTear pieces {WearNTear.GetAllInstances().Count}, smoke {Smoke.GetTotalSmoke()}, fixedDelta {Time.fixedDeltaTime * 1000f:0.#} ms, maxDelta {Time.maximumDeltaTime * 1000f:0} ms, vsync {QualitySettings.vSyncCount}, lodBias {QualitySettings.lodBias:0.##}, shadowDist {QualitySettings.shadowDistance:0}, cascades {QualitySettings.shadowCascades}, texLimit {QualitySettings.globalTextureMipmapLimit}, screen {Screen.width}x{Screen.height}");
			stringBuilder.AppendLine(string.Format("  Hitches (>{0:0} ms) this window: {1}{2}", Settings.HitchThresholdMs.Value, _hitchesThisWindow, (_hitchesSuppressed > 0) ? $" ({_hitchesSuppressed} not logged)" : ""));
			string text = BuildingStats.Report();
			if (text != null)
			{
				stringBuilder.AppendLine(text);
			}
			_hitchesThisWindow = 0;
			_hitchesSuppressed = 0;
			stringBuilder.AppendLine("  ms/frame   max ms   calls/frame  scope");
			int num2 = 0;
			foreach (KeyValuePair<string, Stat> item in list)
			{
				if (num2++ >= 20)
				{
					break;
				}
				Stat value = item.Value;
				double num3 = (double)value.Ticks * TickToMs / (double)_windowFrames;
				stringBuilder.AppendLine($"  {num3,8:0.000} {(double)value.MaxTicks * TickToMs,8:0.00} {(double)value.Calls / (double)_windowFrames,12:0.0}  {item.Key}");
			}
			Plugin.Log.LogInfo((object)stringBuilder.ToString());
			Stats.Clear();
			_cpuMain = (_cpuRender = (_gpu = 0.0));
			_timingSamples = 0;
			_windowFrames = 0;
			_windowStart = realtimeSinceStartup;
		}
	}
	[HarmonyPatch]
	internal static class RenderingPatches
	{
		[HarmonyPatch(typeof(GraphicsSettingsManager), "ApplyQualitySettings")]
		[HarmonyPostfix]
		private static void ApplyQualitySettings_Postfix()
		{
			ApplyQuality();
		}

		[HarmonyPatch(typeof(GameCamera), "Awake")]
		[HarmonyPostfix]
		private static void GameCamera_Awake_Postfix()
		{
			ApplyCullDistances();
		}

		internal static void ApplyQuality()
		{
			int value = Settings.ParticleRaycastBudget.Value;
			if (value > 0)
			{
				QualitySettings.particleRaycastBudget = value;
			}
			float value2 = Settings.MaxShadowDistance.Value;
			if (value2 > 0f && QualitySettings.shadowDistance > value2)
			{
				QualitySettings.shadowDistance = value2;
			}
		}

		internal static void ApplyCullDistances()
		{
			Camera mainCamera = Utils.GetMainCamera();
			if (!((Object)(object)mainCamera == (Object)null))
			{
				int num = LayerMask.NameToLayer("Default_small");
				if (num >= 0)
				{
					float[] layerCullDistances = mainCamera.layerCullDistances;
					layerCullDistances[num] = Settings.SmallObjectDistance.Value;
					mainCamera.layerCullDistances = layerCullDistances;
					mainCamera.layerCullSpherical = true;
				}
			}
		}
	}
	[HarmonyPatch]
	internal static class WearNTearPatches
	{
		private sealed class SnowState
		{
			public float LastUpdateTime = -1f;
		}

		private const float SnowWriteStep = 0.02f;

		[ThreadStatic]
		private static WearNTear _current;

		[ThreadStatic]
		private static bool _snowWriteSkipped;

		[ThreadStatic]
		private static float _snowSkippedValue;

		private static readonly ConditionalWeakTable<WearNTear, SnowState> SnowStates = new ConditionalWeakTable<WearNTear, SnowState>();

		private static readonly FieldRef<WearNTearUpdater, float> SleepUntil = AccessTools.FieldRefAccess<WearNTearUpdater, float>("m_sleepUntil");

		private static readonly FieldRef<WearNTearUpdater, float> SleepUntilNext = AccessTools.FieldRefAccess<WearNTearUpdater, float>("m_sleepUntilNext");

		private static readonly int AshlandsWearTextureId = Shader.PropertyToID("_AshlandsWearTexture");

		private static int _passIndex = -1;

		[HarmonyPatch(typeof(WearNTear), "UpdateWear")]
		[HarmonyPrefix]
		private static void UpdateWear_Prefix(WearNTear __instance)
		{
			_current = __instance;
			_snowWriteSkipped = false;
		}

		[HarmonyPatch(typeof(WearNTear), "UpdateWear")]
		[HarmonyPostfix]
		private static void UpdateWear_Postfix(WearNTear __instance)
		{
			if (_snowWriteSkipped)
			{
				__instance.m_snowBuildup = _snowSkippedValue;
			}
			_snowWriteSkipped = false;
			_current = null;
		}

		[HarmonyPatch(typeof(WearNTear), "UpdateWear")]
		[HarmonyTranspiler]
		private static IEnumerable<CodeInstruction> UpdateWear_Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			MethodInfo getDeltaTime = AccessTools.PropertyGetter(typeof(Time), "deltaTime");
			MethodInfo zdoSetFloat = AccessTools.Method(typeof(ZDO), "Set", new Type[2]
			{
				typeof(int),
				typeof(float)
			}, (Type[])null);
			MethodInfo ourDelta = AccessTools.Method(typeof(WearNTearPatches), "SnowDeltaTime", (Type[])null, (Type[])null);
			MethodInfo ourSet = AccessTools.Method(typeof(WearNTearPatches), "SetFloat", (Type[])null, (Type[])null);
			int replacedDelta = 0;
			int replacedSet = 0;
			foreach (CodeInstruction instruction in instructions)
			{
				if (CodeInstructionExtensions.Calls(instruction, getDeltaTime))
				{
					yield return CodeInstructionExtensions.MoveLabelsFrom(new CodeInstruction(OpCodes.Call, (object)ourDelta), instruction);
					replacedDelta++;
				}
				else if (CodeInstructionExtensions.Calls(instruction, zdoSetFloat))
				{
					yield return CodeInstructionExtensions.MoveLabelsFrom(new CodeInstruction(OpCodes.Call, (object)ourSet), instruction);
					replacedSet++;
				}
				else
				{
					yield return instruction;
				}
			}
			if (replacedDelta == 0 || replacedSet == 0)
			{
				Plugin.Log.LogWarning((object)$"WearNTear.UpdateWear changed in this game version (deltaTime {replacedDelta}, ZDO.Set {replacedSet}); snow fix partially inactive.");
			}
		}

		private static float SnowDeltaTime()
		{
			WearNTear current = _current;
			if ((Object)(object)current == (Object)null || !Settings.SnowFixFps.Value)
			{
				return Time.deltaTime;
			}
			SnowState orCreateValue = SnowStates.GetOrCreateValue(current);
			float time = Time.time;
			float num = ((orCreateValue.LastUpdateTime < 0f) ? 1f : Mathf.Clamp(time - orCreateValue.LastUpdateTime, 0f, 5f));
			orCreateValue.LastUpdateTime = time;
			return num / Settings.SnowReferenceFps.Value;
		}

		private static void SetFloat(ZDO zdo, int hash, float value)
		{
			if ((Object)(object)_current != (Object)null && Settings.SnowQuantizeWrites.Value && hash == ZDOVars.s_snow && value < 1f)
			{
				float num = zdo.GetFloat(hash, 0f);
				if (Mathf.Abs(value - num) < 0.02f)
				{
					_snowWriteSkipped = true;
					_snowSkippedValue = value;
					return;
				}
			}
			zdo.Set(hash, value);
		}

		[HarmonyPatch(typeof(WearNTearUpdater), "UpdateWearNTear")]
		[HarmonyPrefix]
		private static bool UpdateWearNTear_Prefix(WearNTearUpdater __instance, float deltaTime, float time)
		{
			if (!Settings.SpreadCoverPass.Value)
			{
				return true;
			}
			if (_passIndex >= 0)
			{
				ContinuePass(deltaTime);
			}
			if (!SleepUntilNext.Invoke(__instance).Equals(SleepUntil.Invoke(__instance)))
			{
				return true;
			}
			SleepUntilNext.Invoke(__instance) = time + 1f;
			Shader.SetGlobalTexture(AshlandsWearTextureId, (Texture)(object)__instance.m_ashlandsWearTexture);
			if (_passIndex >= 0)
			{
				FinishPass(deltaTime);
			}
			_passIndex = 0;
			ContinuePass(deltaTime);
			return false;
		}

		private static void ContinuePass(float deltaTime)
		{
			List<WearNTear> allInstances = WearNTear.GetAllInstances();
			int num = Mathf.Max(Settings.CoverPassPiecesPerFrame.Value, 1);
			int num2 = Mathf.Min(_passIndex + num, allInstances.Count);
			float time = Time.time;
			for (int i = _passIndex; i < num2; i++)
			{
				WearNTear val = allInstances[i];
				if (!((Object)(object)val == (Object)null))
				{
					if (((Behaviour)val).enabled)
					{
						val.UpdateCover(deltaTime);
					}
					val.UpdateAshlandsMaterialValues(time);
				}
			}
			_passIndex = ((num2 >= allInstances.Count) ? (-1) : num2);
		}

		private static void FinishPass(float deltaTime)
		{
			while (_passIndex >= 0)
			{
				ContinuePass(deltaTime);
			}
		}
	}
	[HarmonyPatch]
	internal static class WeatherPatches
	{
		private sealed class Original
		{
			public float RateOverTime;

			public float RateOverDistance;

			public int MaxParticles;

			public bool Collision;

			public bool Lights;

			public bool Trails;
		}

		private static readonly ConditionalWeakTable<ParticleSystem, Original> Originals = new ConditionalWeakTable<ParticleSystem, Original>();

		[HarmonyPatch(typeof(EnvMan), "SetParticleArrayEnabled")]
		[HarmonyPrefix]
		private static void SetParticleArrayEnabled_Prefix(GameObject[] psystems)
		{
			if (psystems == null)
			{
				return;
			}
			foreach (GameObject val in psystems)
			{
				if (!((Object)(object)val == (Object)null))
				{
					ParticleSystem[] componentsInChildren = val.GetComponentsInChildren<ParticleSystem>(true);
					for (int j = 0; j < componentsInChildren.Length; j++)
					{
						Apply(componentsInChildren[j]);
					}
				}
			}
		}

		internal static void ReapplyAll()
		{
			EnvMan instance = EnvMan.instance;
			if (!((Object)(object)instance == (Object)null))
			{
				ParticleSystem[] componentsInChildren = ((Component)instance).GetComponentsInChildren<ParticleSystem>(true);
				for (int i = 0; i < componentsInChildren.Length; i++)
				{
					Apply(componentsInChildren[i]);
				}
			}
		}

		private static void Apply(ParticleSystem ps)
		{
			//IL_00a6: 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_00cb: 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_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: 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_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0114: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			if (!Originals.TryGetValue(ps, out var value))
			{
				EmissionModule emission = ps.emission;
				Original obj = new Original
				{
					RateOverTime = ((EmissionModule)(ref emission)).rateOverTimeMultiplier,
					RateOverDistance = ((EmissionModule)(ref emission)).rateOverDistanceMultiplier
				};
				MainModule main = ps.main;
				obj.MaxParticles = ((MainModule)(ref main)).maxParticles;
				CollisionModule collision = ps.collision;
				obj.Collision = ((CollisionModule)(ref collision)).enabled;
				LightsModule lights = ps.lights;
				obj.Lights = ((LightsModule)(ref lights)).enabled;
				TrailModule trails = ps.trails;
				obj.Trails = ((TrailModule)(ref trails)).enabled;
				value = obj;
				Originals.Add(ps, value);
			}
			float value2 = Settings.WeatherDensity.Value;
			EmissionModule emission2 = ps.emission;
			((EmissionModule)(ref emission2)).rateOverTimeMultiplier = value.RateOverTime * value2;
			((EmissionModule)(ref emission2)).rateOverDistanceMultiplier = value.RateOverDistance * value2;
			MainModule main2 = ps.main;
			((MainModule)(ref main2)).maxParticles = Mathf.Max(8, Mathf.RoundToInt((float)value.MaxParticles * value2));
			CollisionModule collision2 = ps.collision;
			((CollisionModule)(ref collision2)).enabled = value.Collision && !Settings.WeatherNoCollision.Value;
			LightsModule lights2 = ps.lights;
			((LightsModule)(ref lights2)).enabled = value.Lights && !Settings.WeatherNoLights.Value;
			TrailModule trails2 = ps.trails;
			((TrailModule)(ref trails2)).enabled = value.Trails && !Settings.WeatherNoTrails.Value;
		}
	}
}