Decompiled source of lc memsaver v1.1.0

RuntimeCollective.lc-memsaver.dll

Decompiled 8 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using LobbyCompatibility.Attributes;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.Profiling;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using lc_memsaver.Config;
using lc_memsaver.Managers;
using lc_memsaver.Patches;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: IgnoresAccessChecksTo("AmazingAssets.TerrainToMesh")]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp-firstpass")]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: IgnoresAccessChecksTo("ClientNetworkTransform")]
[assembly: IgnoresAccessChecksTo("com.olegknyazev.softmask")]
[assembly: IgnoresAccessChecksTo("DissonanceVoip")]
[assembly: IgnoresAccessChecksTo("EasyTextEffects")]
[assembly: IgnoresAccessChecksTo("Facepunch Transport for Netcode for GameObjects")]
[assembly: IgnoresAccessChecksTo("Facepunch.Steamworks.Win64")]
[assembly: IgnoresAccessChecksTo("Unity.AI.Navigation")]
[assembly: IgnoresAccessChecksTo("Unity.Animation.Rigging")]
[assembly: IgnoresAccessChecksTo("Unity.Animation.Rigging.DocCodeExamples")]
[assembly: IgnoresAccessChecksTo("Unity.Burst")]
[assembly: IgnoresAccessChecksTo("Unity.Burst.Unsafe")]
[assembly: IgnoresAccessChecksTo("Unity.Collections")]
[assembly: IgnoresAccessChecksTo("Unity.Collections.LowLevel.ILSupport")]
[assembly: IgnoresAccessChecksTo("Unity.InputSystem")]
[assembly: IgnoresAccessChecksTo("Unity.InputSystem.ForUI")]
[assembly: IgnoresAccessChecksTo("Unity.Jobs")]
[assembly: IgnoresAccessChecksTo("Unity.Mathematics")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.Common")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.MetricTypes")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStats")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsMonitor.Component")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsMonitor.Configuration")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsMonitor.Implementation")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsReporting")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetworkProfiler.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetworkSolutionInterface")]
[assembly: IgnoresAccessChecksTo("Unity.Netcode.Components")]
[assembly: IgnoresAccessChecksTo("Unity.Netcode.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.Networking.Transport")]
[assembly: IgnoresAccessChecksTo("Unity.ProBuilder.Csg")]
[assembly: IgnoresAccessChecksTo("Unity.ProBuilder")]
[assembly: IgnoresAccessChecksTo("Unity.ProBuilder.KdTree")]
[assembly: IgnoresAccessChecksTo("Unity.ProBuilder.Poly2Tri")]
[assembly: IgnoresAccessChecksTo("Unity.ProBuilder.Stl")]
[assembly: IgnoresAccessChecksTo("Unity.Profiling.Core")]
[assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.Core.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.Core.ShaderLibrary")]
[assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.HighDefinition.Config.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.HighDefinition.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Authentication")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Analytics")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Components")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Configuration")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Device")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Environments")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Environments.Internal")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Internal")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Networking")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Registration")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Scheduler")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Telemetry")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Threading")]
[assembly: IgnoresAccessChecksTo("Unity.Services.QoS")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Relay")]
[assembly: IgnoresAccessChecksTo("Unity.TextMeshPro")]
[assembly: IgnoresAccessChecksTo("Unity.Timeline")]
[assembly: IgnoresAccessChecksTo("Unity.VisualEffectGraph.Runtime")]
[assembly: IgnoresAccessChecksTo("UnityEngine.ARModule")]
[assembly: IgnoresAccessChecksTo("UnityEngine.NVIDIAModule")]
[assembly: IgnoresAccessChecksTo("UnityEngine.UI")]
[assembly: AssemblyCompany("RuntimeCollective.lc-memsaver")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.6.0")]
[assembly: AssemblyInformationalVersion("1.0.6+cfaaec08e1f1607b330a469628de37b5121a1d1c")]
[assembly: AssemblyProduct("lc-memsaver")]
[assembly: AssemblyTitle("RuntimeCollective.lc-memsaver")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.6.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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 lc_memsaver
{
	[BepInPlugin("RuntimeCollective.lc-memsaver", "lc-memsaver", "1.0.6")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[LobbyCompatibility(/*Could not decode attribute arguments.*/)]
	public class lc_memsaver : BaseUnityPlugin
	{
		[CompilerGenerated]
		private sealed class <RunInitialOptimization>d__25 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			private Stopwatch <sw>5__2;

			private ThreadPriority <previousLoadPriority>5__3;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <RunInitialOptimization>d__25(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<sw>5__2 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_0038: Unknown result type (might be due to invalid IL or missing references)
				//IL_0042: Expected O, but got Unknown
				//IL_007d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0082: Unknown result type (might be due to invalid IL or missing references)
				//IL_0139: Unknown result type (might be due to invalid IL or missing references)
				//IL_014f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0159: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>2__current = (object)new WaitForSeconds(5f);
					<>1__state = 1;
					return true;
				case 1:
					<>1__state = -1;
					<sw>5__2 = Stopwatch.StartNew();
					Logger.LogInfo((object)"Running initial optimization...");
					LogMemoryUsage("Before Optimization");
					MaterialOptimizationPatch.SuppressLogging = true;
					<previousLoadPriority>5__3 = Application.backgroundLoadingPriority;
					Application.backgroundLoadingPriority = (ThreadPriority)4;
					if (PluginConfig.AggressiveMipmapBias.Value > 0f || PluginConfig.UnloadUnusedTextures.Value)
					{
						<>2__current = TextureOptimizer.OptimizeAllTexturesAggressiveCoroutine(includeUnusedTextureScan: false, PluginConfig.ContinuousOptimizationMaxMsPerFrame.Value);
						<>1__state = 2;
						return true;
					}
					goto IL_00d0;
				case 2:
					<>1__state = -1;
					goto IL_00d0;
				case 3:
					<>1__state = -1;
					goto IL_0101;
				case 4:
					<>1__state = -1;
					<>2__current = MemoryManager.PerformFullCleanupCoroutine();
					<>1__state = 5;
					return true;
				case 5:
					<>1__state = -1;
					Application.backgroundLoadingPriority = <previousLoadPriority>5__3;
					MaterialOptimizationPatch.SuppressLogging = false;
					<>2__current = (object)new WaitForSeconds(2f);
					<>1__state = 6;
					return true;
				case 6:
					{
						<>1__state = -1;
						<sw>5__2.Stop();
						LogMemoryUsage("After Optimization");
						LogDetailedMemoryReport();
						Logger.LogInfo((object)$"Initial optimization completed in {<sw>5__2.ElapsedMilliseconds}ms");
						return false;
					}
					IL_00d0:
					if (PluginConfig.EnableAudioOptimization.Value)
					{
						<>2__current = AudioOptimizer.OptimizeAllLoadedAudioClipsCoroutine(PluginConfig.ContinuousOptimizationMaxMsPerFrame.Value);
						<>1__state = 3;
						return true;
					}
					goto IL_0101;
					IL_0101:
					AssetOptimizer.OptimizeAllAssets();
					<>2__current = null;
					<>1__state = 4;
					return true;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		private float _periodicCleanupTimer;

		private float _continuousOptimizationTimer;

		private bool _hasRunInitialOptimization;

		public static lc_memsaver Instance { get; private set; }

		internal static ManualLogSource Logger { get; private set; }

		internal static Harmony? Harmony { get; set; }

		internal static bool IsLethalSpongeLoaded { get; private set; }

		internal static bool IsLethalPerformanceLoaded { get; private set; }

		private void Awake()
		{
			Logger = ((BaseUnityPlugin)this).Logger;
			Instance = this;
			IsLethalSpongeLoaded = Chainloader.PluginInfos.ContainsKey("LethalSponge");
			IsLethalPerformanceLoaded = Chainloader.PluginInfos.ContainsKey("LethalPerformance");
			if (IsLethalSpongeLoaded)
			{
				Logger.LogInfo((object)"LethalSponge detected — skipping overlapping features.");
			}
			if (IsLethalPerformanceLoaded)
			{
				Logger.LogInfo((object)"LethalPerformance detected — skipping render pipeline changes.");
			}
			PluginConfig.Initialize(((BaseUnityPlugin)this).Config);
			MemoryManager.ApplyGlobalTextureOptimizations();
			Application.quitting += AssetBehaviorManager.SaveNow;
			Patch();
			Logger.LogInfo((object)"RuntimeCollective.lc-memsaver v1.0.6 loaded.");
		}

		private void Update()
		{
			if (!PluginConfig.EnablePeriodicGC.Value)
			{
				return;
			}
			_periodicCleanupTimer += Time.unscaledDeltaTime;
			if (_periodicCleanupTimer >= PluginConfig.PeriodicGCIntervalSeconds.Value)
			{
				_periodicCleanupTimer = 0f;
				MemoryManager.PerformLightCleanup();
			}
			if (PluginConfig.EnableContinuousOptimization.Value && (Object)(object)StartOfRound.Instance != (Object)null)
			{
				_continuousOptimizationTimer += Time.unscaledDeltaTime;
				if (_continuousOptimizationTimer >= PluginConfig.ContinuousOptimizationIntervalSeconds.Value)
				{
					_continuousOptimizationTimer = 0f;
					((MonoBehaviour)this).StartCoroutine(MemoryManager.PerformContinuousOptimizationCoroutine());
				}
			}
			if (!_hasRunInitialOptimization && (Object)(object)StartOfRound.Instance != (Object)null)
			{
				_hasRunInitialOptimization = true;
				((MonoBehaviour)this).StartCoroutine(RunInitialOptimization());
			}
			AssetBehaviorManager.TickAutosave();
		}

		[IteratorStateMachine(typeof(<RunInitialOptimization>d__25))]
		private IEnumerator RunInitialOptimization()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <RunInitialOptimization>d__25(0);
		}

		internal static void LogMemoryUsage(string context)
		{
			if (PluginConfig.EnableMemoryLogging.Value)
			{
				long totalMemory = GC.GetTotalMemory(forceFullCollection: false);
				long totalAllocatedMemoryLong = Profiler.GetTotalAllocatedMemoryLong();
				long totalReservedMemoryLong = Profiler.GetTotalReservedMemoryLong();
				Logger.LogInfo((object)$"[Memory] {context} — Managed: {totalMemory / 1048576}MB | Allocated: {totalAllocatedMemoryLong / 1048576}MB | Reserved: {totalReservedMemoryLong / 1048576}MB");
			}
		}

		private static void LogDetailedMemoryReport()
		{
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Invalid comparison between Unknown and I4
			//IL_0264: Unknown result type (might be due to invalid IL or missing references)
			if (!PluginConfig.EnableMemoryLogging.Value)
			{
				return;
			}
			Texture2D[] array = Resources.FindObjectsOfTypeAll<Texture2D>();
			long num = 0L;
			List<(string, long, int, int, TextureFormat)> list = new List<(string, long, int, int, TextureFormat)>();
			Texture2D[] array2 = array;
			foreach (Texture2D val in array2)
			{
				if (!((Object)(object)val == (Object)null))
				{
					long num2 = EstimateTextureMemory(val);
					num += num2;
					list.Add((((Object)val).name, num2, ((Texture)val).width, ((Texture)val).height, val.format));
				}
			}
			AudioClip[] array3 = Resources.FindObjectsOfTypeAll<AudioClip>();
			long num3 = 0L;
			int num4 = 0;
			List<(string, long, float)> list2 = new List<(string, long, float)>();
			AudioClip[] array4 = array3;
			foreach (AudioClip val2 in array4)
			{
				if (!((Object)(object)val2 == (Object)null) && (int)val2.loadState == 2)
				{
					long num5 = (long)val2.samples * (long)val2.channels * 2;
					num3 += num5;
					num4++;
					list2.Add((((Object)val2).name, num5, val2.length));
				}
			}
			Mesh[] array5 = Resources.FindObjectsOfTypeAll<Mesh>();
			long num6 = 0L;
			Mesh[] array6 = array5;
			foreach (Mesh val3 in array6)
			{
				if ((Object)(object)val3 != (Object)null && val3.isReadable)
				{
					num6 += (long)val3.vertexCount * 32L;
				}
			}
			int num7 = Resources.FindObjectsOfTypeAll<AssetBundle>().Length;
			long num8 = num + num3 + num6;
			Logger.LogInfo((object)$"[Memory] Asset breakdown — Textures: {num / 1048576}MB ({array.Length}) | Audio: {num3 / 1048576}MB ({num4} loaded) | Meshes: {num6 / 1048576}MB ({array5.Length}) | Bundles: {num7} | Total: ~{num8 / 1048576}MB");
			list.Sort(((string name, long size, int w, int h, TextureFormat fmt) a, (string name, long size, int w, int h, TextureFormat fmt) b) => b.size.CompareTo(a.size));
			for (int l = 0; l < Math.Min(5, list.Count); l++)
			{
				(string, long, int, int, TextureFormat) tuple = list[l];
				Logger.LogInfo((object)$"[Memory]   Top texture #{l + 1}: '{tuple.Item1}' {tuple.Item3}x{tuple.Item4} ({tuple.Item5}) ~{tuple.Item2 / 1048576}MB");
			}
			list2.Sort(((string name, long size, float length) a, (string name, long size, float length) b) => b.size.CompareTo(a.size));
			for (int m = 0; m < Math.Min(5, list2.Count); m++)
			{
				(string, long, float) tuple2 = list2[m];
				Logger.LogInfo((object)$"[Memory]   Top audio #{m + 1}: '{tuple2.Item1}' {tuple2.Item3:F1}s ~{tuple2.Item2 / 1048576}MB");
			}
		}

		private static long EstimateTextureMemory(Texture2D texture)
		{
			//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_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Invalid comparison between Unknown and I4
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Invalid comparison between Unknown and I4
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected I4, but got Unknown
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Invalid comparison between Unknown and I4
			//IL_0020: 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_003d: Expected I4, but got Unknown
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Invalid comparison between Unknown and I4
			TextureFormat format = texture.format;
			int num;
			if ((int)format <= 14)
			{
				switch (format - 3)
				{
				case 1:
					goto IL_0050;
				case 2:
					goto IL_0055;
				case 0:
					goto IL_005a;
				}
				switch (format - 10)
				{
				case 4:
					break;
				case 0:
					goto IL_0064;
				case 2:
					goto IL_0068;
				default:
					goto IL_0078;
				}
				num = 32;
			}
			else if ((int)format != 25)
			{
				if ((int)format != 47)
				{
					if ((int)format != 48)
					{
						goto IL_0078;
					}
					num = 8;
				}
				else
				{
					num = 8;
				}
			}
			else
			{
				num = 8;
			}
			goto IL_007b;
			IL_005a:
			num = 24;
			goto IL_007b;
			IL_0078:
			num = 16;
			goto IL_007b;
			IL_007b:
			int num2 = num;
			long num3 = (long)((Texture)texture).width * (long)((Texture)texture).height * num2 / 8;
			if (((Texture)texture).mipmapCount > 1)
			{
				num3 = (long)((float)num3 * 1.33f);
			}
			return num3;
			IL_0068:
			num = 8;
			goto IL_007b;
			IL_0064:
			num = 4;
			goto IL_007b;
			IL_0055:
			num = 32;
			goto IL_007b;
			IL_0050:
			num = 32;
			goto IL_007b;
		}

		internal static void Patch()
		{
			//IL_000c: 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_0017: Expected O, but got Unknown
			if (Harmony == null)
			{
				Harmony = new Harmony("RuntimeCollective.lc-memsaver");
			}
			Logger.LogDebug((object)"Patching...");
			Harmony.PatchAll();
			Logger.LogDebug((object)"Finished patching!");
		}

		internal static void Unpatch()
		{
			Logger.LogDebug((object)"Unpatching...");
			Harmony? harmony = Harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			Logger.LogDebug((object)"Finished unpatching!");
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "RuntimeCollective.lc-memsaver";

		public const string PLUGIN_NAME = "lc-memsaver";

		public const string PLUGIN_VERSION = "1.0.6";
	}
}
namespace lc_memsaver.Patches
{
	[HarmonyPatch(typeof(AudioSource), "PlayOneShot")]
	public static class AudioPlayOneShotPatch
	{
		[HarmonyPrefix]
		[HarmonyPatch(new Type[]
		{
			typeof(AudioClip),
			typeof(float)
		})]
		private static void Prefix(AudioClip clip, float volumeScale)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Invalid comparison between Unknown and I4
			if (PluginConfig.EnableAudioOptimization.Value && PluginConfig.ProtectOneShotAudioClips.Value && !((Object)(object)clip == (Object)null))
			{
				bool flag = (int)clip.loadState != 2;
				if (flag)
				{
					clip.LoadAudioData();
				}
				AudioOptimizer.MarkClipUsed(clip);
				AssetBehaviorManager.MarkAudioPlayed(clip, wasOneShot: true, flag);
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch(new Type[] { typeof(AudioClip) })]
		private static void Prefix(AudioClip clip)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Invalid comparison between Unknown and I4
			if (PluginConfig.EnableAudioOptimization.Value && PluginConfig.ProtectOneShotAudioClips.Value && !((Object)(object)clip == (Object)null))
			{
				bool flag = (int)clip.loadState != 2;
				if (flag)
				{
					clip.LoadAudioData();
				}
				AudioOptimizer.MarkClipUsed(clip);
				AssetBehaviorManager.MarkAudioPlayed(clip, wasOneShot: true, flag);
			}
		}
	}
	[HarmonyPatch(typeof(AudioClip), "UnloadAudioData")]
	public static class AudioUnloadDataPatch
	{
		private static int _blocked;

		[HarmonyPrefix]
		private static bool Prefix(AudioClip __instance, ref bool __result)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			if (!PluginConfig.PreventVoiceChatAudioUnload.Value)
			{
				return true;
			}
			if ((Object)(object)__instance == (Object)null)
			{
				return true;
			}
			if ((((Object)__instance).hideFlags & 0x34) != 0)
			{
				return Block(__instance, ref __result, "DontSave");
			}
			string text = ((Object)__instance).name ?? string.Empty;
			if (text.IndexOf("microphone", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return Block(__instance, ref __result, "microphone");
			}
			if (text.IndexOf("dissonance", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return Block(__instance, ref __result, "dissonance");
			}
			return true;
		}

		private static bool Block(AudioClip clip, ref bool result, string reason)
		{
			result = false;
			_blocked++;
			AssetBehaviorManager.MarkAudioUnloadBlocked(clip);
			if (_blocked <= 10 || _blocked % 200 == 0)
			{
				lc_memsaver.Logger.LogDebug((object)$"[Compat] Blocked UnloadAudioData on '{((Object)clip).name}' ({reason}). Count={_blocked}");
			}
			return false;
		}
	}
	[HarmonyPatch]
	public class MaterialOptimizationPatch
	{
		private static readonly Dictionary<int, Material> SharedMaterialCache = new Dictionary<int, Material>();

		public static bool SuppressLogging { get; set; }

		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		[HarmonyPrefix]
		private static void OnGetMaterial(Renderer __instance)
		{
			if (PluginConfig.EnableMemoryLogging.Value && !SuppressLogging)
			{
				GameObject gameObject = ((Component)__instance).gameObject;
				string text = ((gameObject != null) ? ((Object)gameObject).name : null) ?? "Unknown";
				lc_memsaver.Logger.LogDebug((object)("[MaterialTracker] Renderer.material accessed on '" + text + "' - this creates a material copy!"));
			}
		}

		public static void ClearMaterialCache()
		{
			SharedMaterialCache.Clear();
		}
	}
	[HarmonyPatch(typeof(Mesh), "GetTriangles", new Type[] { typeof(int) })]
	public static class MeshGetTrianglesPatch
	{
		private static int _prevented;

		[HarmonyPrefix]
		private static bool Prefix(Mesh __instance, int submesh, ref int[] __result)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: 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_0026: Invalid comparison between Unknown and I4
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			if (!PluginConfig.PreventGetTrianglesOnNonTriangleMeshes.Value)
			{
				return true;
			}
			if ((Object)(object)__instance == (Object)null)
			{
				return true;
			}
			MeshTopology topology = __instance.GetTopology(submesh);
			if ((int)topology == 0 || (int)topology == 2)
			{
				return true;
			}
			__result = Array.Empty<int>();
			_prevented++;
			if (_prevented <= 10 || _prevented % 200 == 0)
			{
				lc_memsaver.Logger.LogDebug((object)$"[Compat] Suppressed GetTriangles on mesh '{((Object)__instance).name}' (sub={submesh}, topo={topology}). Count={_prevented}");
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(Mesh), "UploadMeshData")]
	public static class MeshUploadDataPatch
	{
		private static int _preventedCount;

		[HarmonyPrefix]
		private static void Prefix(Mesh __instance, ref bool markNoLongerReadable)
		{
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Invalid comparison between Unknown and I4
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			if (!markNoLongerReadable || (Object)(object)__instance == (Object)null)
			{
				return;
			}
			if (PluginConfig.PreventNonReadableMeshesGlobally.Value)
			{
				markNoLongerReadable = false;
				_preventedCount++;
				if (_preventedCount <= 10 || _preventedCount % 200 == 0)
				{
					lc_memsaver.Logger.LogDebug((object)$"[Compat] Forced UploadMeshData(false) on mesh '{((Object)__instance).name}' (verts={__instance.vertexCount}, hideFlags={((Object)__instance).hideFlags}). Count={_preventedCount}");
				}
			}
			else
			{
				if (!PluginConfig.PreventNonReadableDynamicMeshes.Value)
				{
					return;
				}
				bool flag = string.IsNullOrWhiteSpace(((Object)__instance).name);
				bool flag2 = (((Object)__instance).hideFlags & 0x34) > 0;
				bool flag3 = __instance.vertexCount > 0 && __instance.vertexCount < 512;
				if (flag || flag2 || flag3)
				{
					markNoLongerReadable = false;
					_preventedCount++;
					if (_preventedCount <= 10 || _preventedCount % 100 == 0)
					{
						lc_memsaver.Logger.LogDebug((object)$"[Compat] Prevented UploadMeshData(true) on dynamic mesh '{((Object)__instance).name}' (verts={__instance.vertexCount}, hideFlags={((Object)__instance).hideFlags}). Count={_preventedCount}");
					}
				}
			}
		}
	}
	[HarmonyPatch(typeof(RoundManager))]
	public class RoundManagerPatch
	{
		[HarmonyPatch("LoadNewLevel")]
		[HarmonyPrefix]
		private static void OnLoadNewLevelPrefix(RoundManager __instance)
		{
			if (PluginConfig.EnableAggressiveLevelTransitionCleanup.Value)
			{
				lc_memsaver.Logger.LogDebug((object)"Level loading — cleaning previous level...");
				AssetOptimizer.ClearCaches();
				((MonoBehaviour)lc_memsaver.Instance).StartCoroutine(MemoryManager.PerformFullCleanupCoroutine());
			}
		}

		[HarmonyPatch("FinishGeneratingNewLevelClientRpc")]
		[HarmonyPostfix]
		private static void OnFinishGeneratingPostfix(RoundManager __instance)
		{
			Stopwatch stopwatch = Stopwatch.StartNew();
			lc_memsaver.Logger.LogDebug((object)"Level generated — optimizing assets...");
			MaterialOptimizationPatch.SuppressLogging = true;
			if (PluginConfig.EnableTextureOptimization.Value && !lc_memsaver.IsLethalSpongeLoaded && (PluginConfig.AggressiveMipmapBias.Value > 0f || PluginConfig.UnloadUnusedTextures.Value))
			{
				((MonoBehaviour)lc_memsaver.Instance).StartCoroutine(TextureOptimizer.OptimizeAllTexturesAggressiveCoroutine(includeUnusedTextureScan: false, PluginConfig.ContinuousOptimizationMaxMsPerFrame.Value));
			}
			if (PluginConfig.EnableAudioOptimization.Value)
			{
				((MonoBehaviour)lc_memsaver.Instance).StartCoroutine(AudioOptimizer.PreloadReferencedAudioClipsCoroutine(PluginConfig.ContinuousOptimizationMaxMsPerFrame.Value));
			}
			AssetOptimizer.OptimizeAllAssets();
			MemoryManager.PerformLightCleanup();
			MaterialOptimizationPatch.SuppressLogging = false;
			stopwatch.Stop();
			lc_memsaver.Logger.LogDebug((object)$"Level optimized in {stopwatch.ElapsedMilliseconds}ms");
		}

		[HarmonyPatch("UnloadSceneObjectsEarly")]
		[HarmonyPostfix]
		private static void OnUnloadScenePostfix()
		{
			if (PluginConfig.EnableAggressiveLevelTransitionCleanup.Value)
			{
				lc_memsaver.Logger.LogDebug((object)"Unloading scene objects — cleanup...");
				((MonoBehaviour)lc_memsaver.Instance).StartCoroutine(MemoryManager.PerformFullCleanupCoroutine());
			}
		}

		[HarmonyPatch("GenerateNewFloor")]
		[HarmonyPostfix]
		private static void OnGenerateNewFloorPostfix()
		{
			lc_memsaver.Logger.LogDebug((object)"Floor generated — optimizing interior...");
			MaterialOptimizationPatch.SuppressLogging = true;
			if (PluginConfig.EnableTextureOptimization.Value && !lc_memsaver.IsLethalSpongeLoaded && (PluginConfig.AggressiveMipmapBias.Value > 0f || PluginConfig.UnloadUnusedTextures.Value))
			{
				((MonoBehaviour)lc_memsaver.Instance).StartCoroutine(TextureOptimizer.OptimizeAllTexturesAggressiveCoroutine(includeUnusedTextureScan: false, PluginConfig.ContinuousOptimizationMaxMsPerFrame.Value));
			}
			MemoryManager.PerformLightCleanup();
			MaterialOptimizationPatch.SuppressLogging = false;
		}
	}
	[HarmonyPatch]
	public class SceneManagerPatch
	{
		private static bool _initialized;

		[HarmonyPatch(typeof(GameNetworkManager), "Start")]
		[HarmonyPostfix]
		private static void OnGameNetworkManagerStart()
		{
			if (!_initialized)
			{
				_initialized = true;
				SceneManager.sceneUnloaded += OnSceneUnloaded;
				SceneManager.sceneLoaded += OnSceneLoaded;
				lc_memsaver.Logger.LogDebug((object)"Scene transition hooks registered.");
			}
		}

		private static void OnSceneUnloaded(Scene scene)
		{
			if (PluginConfig.EnableAggressiveLevelTransitionCleanup.Value)
			{
				lc_memsaver.Logger.LogDebug((object)("Scene '" + ((Scene)(ref scene)).name + "' unloaded."));
				MemoryManager.PerformLightCleanup();
			}
		}

		private static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Invalid comparison between Unknown and I4
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			lc_memsaver.Logger.LogDebug((object)$"Scene '{((Scene)(ref scene)).name}' loaded ({mode}).");
			MaterialOptimizationPatch.SuppressLogging = true;
			if ((int)mode == 1)
			{
				if (PluginConfig.EnableTextureOptimization.Value && !lc_memsaver.IsLethalSpongeLoaded && (PluginConfig.AggressiveMipmapBias.Value > 0f || PluginConfig.UnloadUnusedTextures.Value))
				{
					((MonoBehaviour)lc_memsaver.Instance).StartCoroutine(TextureOptimizer.OptimizeAllTexturesAggressiveCoroutine(includeUnusedTextureScan: false, PluginConfig.ContinuousOptimizationMaxMsPerFrame.Value));
				}
				AssetOptimizer.OptimizeAllAssets();
				if (PluginConfig.EnableAudioOptimization.Value)
				{
					((MonoBehaviour)lc_memsaver.Instance).StartCoroutine(AudioOptimizer.PreloadReferencedAudioClipsCoroutine(PluginConfig.ContinuousOptimizationMaxMsPerFrame.Value));
				}
			}
			if ((int)mode == 0)
			{
				AssetOptimizer.ClearCaches();
				((MonoBehaviour)lc_memsaver.Instance).StartCoroutine(MemoryManager.PerformFullCleanupCoroutine());
				if (PluginConfig.EnableAudioOptimization.Value)
				{
					((MonoBehaviour)lc_memsaver.Instance).StartCoroutine(AudioOptimizer.PreloadReferencedAudioClipsCoroutine(PluginConfig.ContinuousOptimizationMaxMsPerFrame.Value));
				}
			}
			MaterialOptimizationPatch.SuppressLogging = false;
			lc_memsaver.LogMemoryUsage("After Loading '" + ((Scene)(ref scene)).name + "'");
		}
	}
	[HarmonyPatch(typeof(RoundManager), "SpawnMapObjects")]
	public static class SpawnMapObjectsCompatibilityPatch
	{
		[HarmonyFinalizer]
		private static Exception? Finalizer(Exception? __exception)
		{
			if (__exception == null)
			{
				return null;
			}
			if (!PluginConfig.SuppressSpawnMapObjectsExceptions.Value)
			{
				return __exception;
			}
			lc_memsaver.Logger.LogWarning((object)("[Compat] Suppressed exception in RoundManager.SpawnMapObjects: " + __exception.GetType().Name + ": " + __exception.Message));
			return null;
		}
	}
	[HarmonyPatch(typeof(StartOfRound))]
	public class StartOfRoundPatch
	{
		[HarmonyPatch("Start")]
		[HarmonyPostfix]
		private static void OnStartPostfix(StartOfRound __instance)
		{
			lc_memsaver.Logger.LogDebug((object)"Game session started.");
		}

		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		[HarmonyPostfix]
		private static void OnOpeningDoorsPostfix()
		{
			if (PluginConfig.EnableAggressiveLevelTransitionCleanup.Value)
			{
				lc_memsaver.Logger.LogDebug((object)"Landed — light cleanup.");
				MemoryManager.PerformLightCleanup();
			}
		}

		[HarmonyPatch("ShipLeave")]
		[HarmonyPostfix]
		private static void OnShipLeavePostfix()
		{
			if (PluginConfig.EnableAggressiveLevelTransitionCleanup.Value)
			{
				lc_memsaver.Logger.LogDebug((object)"Ship leaving — full cleanup.");
				((MonoBehaviour)lc_memsaver.Instance).StartCoroutine(MemoryManager.PerformFullCleanupCoroutine());
			}
		}

		[HarmonyPatch("EndOfGame")]
		[HarmonyPostfix]
		private static void OnEndOfGamePostfix()
		{
			if (PluginConfig.EnableAggressiveLevelTransitionCleanup.Value)
			{
				lc_memsaver.Logger.LogDebug((object)"Round ended — cleanup.");
				((MonoBehaviour)lc_memsaver.Instance).StartCoroutine(MemoryManager.PerformFullCleanupCoroutine());
			}
		}
	}
	[HarmonyPatch(typeof(Texture2D), "Compress")]
	public static class TextureCompressPatch
	{
		private static int _skipped;

		[HarmonyPrefix]
		private static bool Prefix(Texture2D __instance, bool highQuality)
		{
			if (!PluginConfig.PreventUnsafeRuntimeTextureCompress.Value)
			{
				return true;
			}
			if ((Object)(object)__instance == (Object)null)
			{
				return true;
			}
			if (!((Texture)__instance).isReadable)
			{
				LogSkip(__instance, "not readable");
				return false;
			}
			if (((uint)((Texture)__instance).width & 3u) != 0 || ((uint)((Texture)__instance).height & 3u) != 0)
			{
				LogSkip(__instance, $"size {((Texture)__instance).width}x{((Texture)__instance).height} not multiple of 4");
				return false;
			}
			return true;
		}

		private static void LogSkip(Texture2D tex, string reason)
		{
			_skipped++;
			if (_skipped <= 10 || _skipped % 200 == 0)
			{
				lc_memsaver.Logger.LogDebug((object)$"[Compat] Skipped Texture2D.Compress() on '{((Object)tex).name}' ({reason}). Count={_skipped}");
			}
		}
	}
}
namespace lc_memsaver.Managers
{
	public static class AssetBehaviorManager
	{
		[Serializable]
		private class BehaviorDb
		{
			public int version = 1;

			public List<AudioClipBehavior> audio = new List<AudioClipBehavior>();
		}

		[Serializable]
		public class AudioClipBehavior
		{
			public string key = string.Empty;

			public string name = string.Empty;

			public float length;

			public int plays;

			public int playOneShots;

			public int reloads;

			public int unloadBlocks;

			public float lastUsedUnscaledTime;

			public float lastSeenLength;
		}

		private const string DbFileName = "RuntimeCollective.lc-memsaver.behavior.json";

		private static readonly Dictionary<string, AudioClipBehavior> _audioByKey = new Dictionary<string, AudioClipBehavior>();

		private static bool _loaded;

		private static float _lastSaveTime;

		private static bool _dirty;

		private static string GetDbPath()
		{
			return Path.Combine(Paths.ConfigPath, "RuntimeCollective.lc-memsaver.behavior.json");
		}

		public static void EnsureLoaded()
		{
			if (_loaded)
			{
				return;
			}
			_loaded = true;
			if (!PluginConfig.EnableAssetBehaviorLearning.Value || !PluginConfig.PersistAssetBehaviorToDisk.Value)
			{
				return;
			}
			try
			{
				string dbPath = GetDbPath();
				if (!File.Exists(dbPath))
				{
					return;
				}
				string text = File.ReadAllText(dbPath);
				if (string.IsNullOrWhiteSpace(text))
				{
					return;
				}
				BehaviorDb behaviorDb = JsonUtility.FromJson<BehaviorDb>(text);
				if (behaviorDb?.audio == null)
				{
					return;
				}
				_audioByKey.Clear();
				foreach (AudioClipBehavior item in behaviorDb.audio)
				{
					if (item != null && !string.IsNullOrWhiteSpace(item.key))
					{
						_audioByKey[item.key] = item;
					}
				}
				lc_memsaver.Logger.LogDebug((object)$"[Behavior] Loaded {_audioByKey.Count} audio behavior entries.");
			}
			catch (Exception ex)
			{
				lc_memsaver.Logger.LogDebug((object)("[Behavior] Failed to load behavior DB: " + ex.Message));
			}
		}

		private static string MakeAudioKey(AudioClip clip)
		{
			string text = (((Object)(object)clip != (Object)null) ? (((Object)clip).name ?? string.Empty) : string.Empty);
			int num = (((Object)(object)clip != (Object)null) ? clip.channels : 0);
			int num2 = (((Object)(object)clip != (Object)null) ? clip.frequency : 0);
			float num3 = (((Object)(object)clip != (Object)null) ? clip.length : 0f);
			return $"AudioClip|{text}|{num}|{num2}|{num3:0.000}";
		}

		private static AudioClipBehavior GetOrCreate(AudioClip clip)
		{
			EnsureLoaded();
			string key = MakeAudioKey(clip);
			if (_audioByKey.TryGetValue(key, out AudioClipBehavior value))
			{
				return value;
			}
			AudioClipBehavior audioClipBehavior = new AudioClipBehavior
			{
				key = key,
				name = (((Object)(object)clip != (Object)null) ? ((Object)clip).name : string.Empty),
				length = (((Object)(object)clip != (Object)null) ? clip.length : 0f),
				lastSeenLength = (((Object)(object)clip != (Object)null) ? clip.length : 0f),
				lastUsedUnscaledTime = Time.unscaledTime
			};
			_audioByKey[key] = audioClipBehavior;
			_dirty = true;
			return audioClipBehavior;
		}

		public static void MarkAudioPlayed(AudioClip clip, bool wasOneShot, bool hadToReload)
		{
			if (PluginConfig.EnableAssetBehaviorLearning.Value && !((Object)(object)clip == (Object)null))
			{
				AudioClipBehavior orCreate = GetOrCreate(clip);
				orCreate.plays++;
				if (wasOneShot)
				{
					orCreate.playOneShots++;
				}
				if (hadToReload)
				{
					orCreate.reloads++;
				}
				orCreate.lastUsedUnscaledTime = Time.unscaledTime;
				orCreate.lastSeenLength = clip.length;
				_dirty = true;
			}
		}

		public static void MarkAudioUnloadBlocked(AudioClip clip)
		{
			if (PluginConfig.EnableAssetBehaviorLearning.Value && !((Object)(object)clip == (Object)null))
			{
				AudioClipBehavior orCreate = GetOrCreate(clip);
				orCreate.unloadBlocks++;
				orCreate.lastUsedUnscaledTime = Time.unscaledTime;
				_dirty = true;
			}
		}

		public static bool ShouldKeepAudioLoaded(AudioClip clip)
		{
			if (!PluginConfig.EnableAssetBehaviorLearning.Value)
			{
				return false;
			}
			if ((Object)(object)clip == (Object)null)
			{
				return false;
			}
			AudioClipBehavior orCreate = GetOrCreate(clip);
			if (orCreate.playOneShots >= 20)
			{
				return true;
			}
			if (orCreate.reloads >= 5)
			{
				return true;
			}
			if (orCreate.plays >= 50)
			{
				return true;
			}
			float value = PluginConfig.ProtectRecentlyUsedAudioSeconds.Value;
			if (value > 0f && Time.unscaledTime - orCreate.lastUsedUnscaledTime <= value)
			{
				return true;
			}
			return false;
		}

		public static void TickAutosave()
		{
			if (PluginConfig.EnableAssetBehaviorLearning.Value && PluginConfig.PersistAssetBehaviorToDisk.Value && _dirty)
			{
				float num = PluginConfig.AssetBehaviorSaveIntervalSeconds.Value;
				if (num <= 0f)
				{
					num = 120f;
				}
				if (!(Time.unscaledTime - _lastSaveTime < num))
				{
					SaveNow();
				}
			}
		}

		public static void SaveNow()
		{
			if (!PluginConfig.EnableAssetBehaviorLearning.Value || !PluginConfig.PersistAssetBehaviorToDisk.Value || !_dirty)
			{
				return;
			}
			try
			{
				BehaviorDb behaviorDb = new BehaviorDb();
				behaviorDb.audio.AddRange(_audioByKey.Values);
				string contents = JsonUtility.ToJson((object)behaviorDb, true);
				File.WriteAllText(GetDbPath(), contents);
				_dirty = false;
				_lastSaveTime = Time.unscaledTime;
			}
			catch (Exception ex)
			{
				lc_memsaver.Logger.LogDebug((object)("[Behavior] Failed to save behavior DB: " + ex.Message));
			}
		}
	}
	public static class AssetOptimizer
	{
		private static long _totalBytesSaved;

		private static readonly Dictionary<string, Material> _materialCache = new Dictionary<string, Material>();

		public static void OptimizeAllAssets()
		{
			_totalBytesSaved = 0L;
			if (!lc_memsaver.IsLethalSpongeLoaded)
			{
				DeduplicateMaterials();
			}
			CleanupRenderTextures();
			LogShaderDiagnostics();
			lc_memsaver.Logger.LogInfo((object)$"Assets: material dedup + RT cleanup, ~{_totalBytesSaved / 1048576}MB saved.");
		}

		private static void DeduplicateMaterials()
		{
			if (!PluginConfig.EnableMaterialDeduplication.Value)
			{
				return;
			}
			int num = 0;
			long num2 = 0L;
			BuildMaterialCache();
			Renderer[] array = Object.FindObjectsOfType<Renderer>();
			Renderer[] array2 = array;
			foreach (Renderer val in array2)
			{
				try
				{
					Material[] sharedMaterials = val.sharedMaterials;
					bool flag = false;
					for (int j = 0; j < sharedMaterials.Length; j++)
					{
						if ((Object)(object)sharedMaterials[j] == (Object)null)
						{
							continue;
						}
						string name = ((Object)sharedMaterials[j]).name;
						if (name.EndsWith(" (Instance)"))
						{
							string name2 = name.Replace(" (Instance)", "");
							Material val2 = FindSharedMaterial(name2);
							if ((Object)(object)val2 != (Object)null)
							{
								sharedMaterials[j] = val2;
								flag = true;
								num++;
								num2 += 4096;
							}
						}
					}
					if (flag)
					{
						val.sharedMaterials = sharedMaterials;
					}
				}
				catch (Exception ex)
				{
					lc_memsaver.Logger.LogDebug((object)("[AssetOptimizer] Material dedup error on '" + ((Object)val).name + "': " + ex.Message));
				}
			}
			_totalBytesSaved += num2;
			lc_memsaver.Logger.LogDebug((object)$"Material dedup: {num} instances replaced, ~{num2 / 1024}KB saved.");
		}

		private static void BuildMaterialCache()
		{
			_materialCache.Clear();
			Material[] array = Resources.FindObjectsOfTypeAll<Material>();
			Material[] array2 = array;
			foreach (Material val in array2)
			{
				if ((Object)(object)val != (Object)null && !((Object)val).name.Contains("(Instance)") && !_materialCache.ContainsKey(((Object)val).name))
				{
					_materialCache[((Object)val).name] = val;
				}
			}
		}

		private static Material FindSharedMaterial(string name)
		{
			_materialCache.TryGetValue(name, out Material value);
			return value;
		}

		private static void CleanupRenderTextures()
		{
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			RenderTexture[] array = Resources.FindObjectsOfTypeAll<RenderTexture>();
			int num = 0;
			long num2 = 0L;
			Camera[] array2 = Object.FindObjectsOfType<Camera>();
			HashSet<RenderTexture> hashSet = new HashSet<RenderTexture>();
			Camera[] array3 = array2;
			foreach (Camera val in array3)
			{
				if ((Object)(object)val != (Object)null && (Object)(object)val.targetTexture != (Object)null)
				{
					hashSet.Add(val.targetTexture);
				}
			}
			RenderTexture[] array4 = array;
			foreach (RenderTexture val2 in array4)
			{
				if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2 == (Object)(object)RenderTexture.active) && !(((Object)val2).name == "BackBuffer") && !(((Object)val2).name == "") && !((Enum)((Object)val2).hideFlags).HasFlag((Enum)(object)(HideFlags)52) && !hashSet.Contains(val2) && !val2.IsCreated())
				{
					long num3 = (long)((Texture)val2).width * (long)((Texture)val2).height * 4;
					num2 += num3;
					val2.Release();
					num++;
				}
			}
			_totalBytesSaved += num2;
			if (num > 0)
			{
				lc_memsaver.Logger.LogDebug((object)$"RT cleanup: {num} released, ~{num2 / 1048576}MB saved.");
			}
		}

		private static void LogShaderDiagnostics()
		{
			Shader[] array = Resources.FindObjectsOfTypeAll<Shader>();
			lc_memsaver.Logger.LogDebug((object)$"[AssetOptimizer] {array.Length} shaders loaded.");
		}

		public static void ClearCaches()
		{
			_materialCache.Clear();
		}
	}
	public static class AudioOptimizer
	{
		[CompilerGenerated]
		private sealed class <OptimizeAllLoadedAudioClipsCoroutine>d__9 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public int maxMsPerFrame;

			private AudioClip[] <allClips>5__2;

			private HashSet<AudioClip> <playingClips>5__3;

			private HashSet<AudioClip> <referencedClips>5__4;

			private int <maxUnload>5__5;

			private Stopwatch <sw>5__6;

			private AudioClip[] <>7__wrap6;

			private int <>7__wrap7;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <OptimizeAllLoadedAudioClipsCoroutine>d__9(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<allClips>5__2 = null;
				<playingClips>5__3 = null;
				<referencedClips>5__4 = null;
				<sw>5__6 = null;
				<>7__wrap6 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				int num = <>1__state;
				if (num != 0)
				{
					if (num != 1)
					{
						return false;
					}
					<>1__state = -1;
					goto IL_0144;
				}
				<>1__state = -1;
				if (!PluginConfig.EnableAudioOptimization.Value)
				{
					return false;
				}
				if (maxMsPerFrame < 1)
				{
					maxMsPerFrame = 1;
				}
				_totalClipsOptimized = 0;
				_estimatedBytesSaved = 0L;
				<allClips>5__2 = Resources.FindObjectsOfTypeAll<AudioClip>();
				<playingClips>5__3 = BuildPlayingClipSet();
				<referencedClips>5__4 = null;
				if (PluginConfig.ProtectReferencedAudioClips.Value)
				{
					<referencedClips>5__4 = BuildReferencedClipSet();
				}
				<maxUnload>5__5 = PluginConfig.MaxAudioClipsUnloadedPerPass.Value;
				<sw>5__6 = Stopwatch.StartNew();
				<>7__wrap6 = <allClips>5__2;
				<>7__wrap7 = 0;
				goto IL_0152;
				IL_0165:
				<>7__wrap6 = null;
				lc_memsaver.Logger.LogDebug((object)$"[Continuous] Audio: {_totalClipsOptimized}/{<allClips>5__2.Length} clips unloaded, ~{_estimatedBytesSaved / 1048576}MB saved.");
				return false;
				IL_0144:
				<>7__wrap7++;
				goto IL_0152;
				IL_0152:
				if (<>7__wrap7 < <>7__wrap6.Length)
				{
					AudioClip val = <>7__wrap6[<>7__wrap7];
					try
					{
						OptimizeAudioClip(val, <playingClips>5__3, <referencedClips>5__4);
						if (_totalClipsOptimized >= <maxUnload>5__5)
						{
							goto IL_0165;
						}
					}
					catch (Exception ex)
					{
						lc_memsaver.Logger.LogDebug((object)("Could not optimize audio clip '" + ((val != null) ? ((Object)val).name : null) + "': " + ex.Message));
					}
					if (<sw>5__6.ElapsedMilliseconds >= maxMsPerFrame)
					{
						<sw>5__6.Restart();
						<>2__current = null;
						<>1__state = 1;
						return true;
					}
					goto IL_0144;
				}
				goto IL_0165;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[CompilerGenerated]
		private sealed class <PreloadReferencedAudioClipsCoroutine>d__7 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public int maxMsPerFrame;

			private int <maxPreloads>5__2;

			private int <loaded>5__3;

			private Stopwatch <sw>5__4;

			private HashSet<AudioClip>.Enumerator <>7__wrap4;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <PreloadReferencedAudioClipsCoroutine>d__7(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				int num = <>1__state;
				if (num == -3 || num == 1)
				{
					try
					{
					}
					finally
					{
						<>m__Finally1();
					}
				}
				<sw>5__4 = null;
				<>7__wrap4 = default(HashSet<AudioClip>.Enumerator);
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ba: Invalid comparison between Unknown and I4
				try
				{
					switch (<>1__state)
					{
					default:
						return false;
					case 0:
					{
						<>1__state = -1;
						if (!PluginConfig.EnableAudioOptimization.Value)
						{
							return false;
						}
						if (!PluginConfig.PreloadReferencedAudioOnSceneLoad.Value)
						{
							return false;
						}
						if (maxMsPerFrame < 1)
						{
							maxMsPerFrame = 1;
						}
						HashSet<AudioClip> hashSet = BuildReferencedClipSet();
						<maxPreloads>5__2 = PluginConfig.MaxReferencedAudioPreloadsPerPass.Value;
						<loaded>5__3 = 0;
						<sw>5__4 = Stopwatch.StartNew();
						<>7__wrap4 = hashSet.GetEnumerator();
						<>1__state = -3;
						break;
					}
					case 1:
						<>1__state = -3;
						break;
					}
					while (<>7__wrap4.MoveNext())
					{
						AudioClip current = <>7__wrap4.Current;
						if ((Object)(object)current == (Object)null || ShouldSkipClip(current))
						{
							continue;
						}
						if ((int)current.loadState != 2)
						{
							current.LoadAudioData();
							<loaded>5__3++;
							if (<loaded>5__3 >= <maxPreloads>5__2)
							{
								break;
							}
						}
						if (<sw>5__4.ElapsedMilliseconds >= maxMsPerFrame)
						{
							<sw>5__4.Restart();
							<>2__current = null;
							<>1__state = 1;
							return true;
						}
					}
					<>m__Finally1();
					<>7__wrap4 = default(HashSet<AudioClip>.Enumerator);
					if (<loaded>5__3 > 0)
					{
						lc_memsaver.Logger.LogDebug((object)$"[Audio] Preloaded {<loaded>5__3} referenced clips.");
					}
					return false;
				}
				catch
				{
					//try-fault
					((IDisposable)this).Dispose();
					throw;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			private void <>m__Finally1()
			{
				<>1__state = -1;
				((IDisposable)<>7__wrap4).Dispose();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		private static int _totalClipsOptimized;

		private static long _estimatedBytesSaved;

		private static readonly Dictionary<int, float> _recentlyUsedClips = new Dictionary<int, float>();

		internal static void MarkClipUsed(AudioClip clip)
		{
			if (!((Object)(object)clip == (Object)null) && PluginConfig.ProtectOneShotAudioClips.Value)
			{
				_recentlyUsedClips[((Object)clip).GetInstanceID()] = Time.unscaledTime;
			}
		}

		private static bool WasUsedRecently(AudioClip clip)
		{
			float value = PluginConfig.ProtectRecentlyUsedAudioSeconds.Value;
			if (value <= 0f)
			{
				return false;
			}
			if ((Object)(object)clip == (Object)null)
			{
				return false;
			}
			int instanceID = ((Object)clip).GetInstanceID();
			if (!_recentlyUsedClips.TryGetValue(instanceID, out var value2))
			{
				return false;
			}
			return Time.unscaledTime - value2 <= value;
		}

		private static bool ShouldSkipClip(AudioClip clip)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			if ((((Object)clip).hideFlags & 0x34) != 0)
			{
				return true;
			}
			if (string.IsNullOrWhiteSpace(((Object)clip).name))
			{
				return true;
			}
			string name = ((Object)clip).name;
			if (name.IndexOf("microphone", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return true;
			}
			if (name.IndexOf("dissonance", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return true;
			}
			return false;
		}

		private static HashSet<AudioClip> BuildReferencedClipSet()
		{
			HashSet<AudioClip> hashSet = new HashSet<AudioClip>();
			AudioSource[] array = Resources.FindObjectsOfTypeAll<AudioSource>();
			AudioSource[] array2 = array;
			foreach (AudioSource val in array2)
			{
				if (!((Object)(object)val == (Object)null) && (Object)(object)val.clip != (Object)null)
				{
					hashSet.Add(val.clip);
				}
			}
			return hashSet;
		}

		[IteratorStateMachine(typeof(<PreloadReferencedAudioClipsCoroutine>d__7))]
		public static IEnumerator PreloadReferencedAudioClipsCoroutine(int maxMsPerFrame)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <PreloadReferencedAudioClipsCoroutine>d__7(0)
			{
				maxMsPerFrame = maxMsPerFrame
			};
		}

		public static void OptimizeAllLoadedAudioClips()
		{
			if (!PluginConfig.EnableAudioOptimization.Value)
			{
				return;
			}
			_totalClipsOptimized = 0;
			_estimatedBytesSaved = 0L;
			AudioClip[] array = Resources.FindObjectsOfTypeAll<AudioClip>();
			HashSet<AudioClip> playingClips = BuildPlayingClipSet();
			HashSet<AudioClip> referencedClips = null;
			if (PluginConfig.ProtectReferencedAudioClips.Value)
			{
				referencedClips = BuildReferencedClipSet();
			}
			int value = PluginConfig.MaxAudioClipsUnloadedPerPass.Value;
			AudioClip[] array2 = array;
			foreach (AudioClip val in array2)
			{
				try
				{
					OptimizeAudioClip(val, playingClips, referencedClips);
					if (_totalClipsOptimized >= value)
					{
						break;
					}
				}
				catch (Exception ex)
				{
					lc_memsaver.Logger.LogDebug((object)("Could not optimize audio clip '" + ((Object)val).name + "': " + ex.Message));
				}
			}
			lc_memsaver.Logger.LogInfo((object)$"Audio: {_totalClipsOptimized}/{array.Length} clips unloaded, ~{_estimatedBytesSaved / 1048576}MB saved.");
		}

		[IteratorStateMachine(typeof(<OptimizeAllLoadedAudioClipsCoroutine>d__9))]
		public static IEnumerator OptimizeAllLoadedAudioClipsCoroutine(int maxMsPerFrame)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <OptimizeAllLoadedAudioClipsCoroutine>d__9(0)
			{
				maxMsPerFrame = maxMsPerFrame
			};
		}

		private static void OptimizeAudioClip(AudioClip clip, HashSet<AudioClip> playingClips, HashSet<AudioClip>? referencedClips)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Invalid comparison between Unknown and I4
			if ((Object)(object)clip == (Object)null || (int)clip.loadState != 2 || ShouldSkipClip(clip) || playingClips.Contains(clip) || (referencedClips != null && referencedClips.Contains(clip)) || WasUsedRecently(clip) || AssetBehaviorManager.ShouldKeepAudioLoaded(clip))
			{
				return;
			}
			long num = EstimateAudioMemory(clip);
			bool flag = false;
			if (PluginConfig.AggressiveAudioUnload.Value)
			{
				if (clip.length >= 0.5f)
				{
					flag = true;
				}
			}
			else if (clip.length >= PluginConfig.AudioStreamingThresholdSeconds.Value)
			{
				flag = true;
			}
			if (flag)
			{
				clip.UnloadAudioData();
				_estimatedBytesSaved += num;
				_totalClipsOptimized++;
				lc_memsaver.Logger.LogDebug((object)$"Unloaded audio data for '{((Object)clip).name}' ({clip.length:F1}s, ~{num / 1024}KB)");
			}
		}

		private static HashSet<AudioClip> BuildPlayingClipSet()
		{
			HashSet<AudioClip> hashSet = new HashSet<AudioClip>();
			AudioSource[] array = Object.FindObjectsOfType<AudioSource>();
			AudioSource[] array2 = array;
			foreach (AudioSource val in array2)
			{
				if (val.isPlaying && (Object)(object)val.clip != (Object)null)
				{
					hashSet.Add(val.clip);
				}
			}
			return hashSet;
		}

		private static long EstimateAudioMemory(AudioClip clip)
		{
			return (long)clip.samples * (long)clip.channels * 2;
		}

		public static void PreloadCriticalAudio()
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Invalid comparison between Unknown and I4
			AudioSource[] array = Resources.FindObjectsOfTypeAll<AudioSource>();
			AudioSource[] array2 = array;
			foreach (AudioSource val in array2)
			{
				AudioClip val2 = (((Object)(object)val != (Object)null) ? val.clip : null);
				if (!((Object)(object)val2 == (Object)null) && !ShouldSkipClip(val2) && (int)val2.loadState != 2)
				{
					val2.LoadAudioData();
				}
			}
		}
	}
	public static class MemoryManager
	{
		[CompilerGenerated]
		private sealed class <PerformContinuousOptimizationCoroutine>d__5 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			private int <maxMs>5__2;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <PerformContinuousOptimizationCoroutine>d__5(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				int num = <>1__state;
				if (num == -3 || (uint)(num - 1) <= 1u)
				{
					try
					{
					}
					finally
					{
						<>m__Finally1();
					}
				}
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				try
				{
					switch (<>1__state)
					{
					default:
						return false;
					case 0:
					{
						<>1__state = -1;
						if (_isCleaningUp)
						{
							return false;
						}
						if (_isContinuousOptimizing)
						{
							return false;
						}
						_isContinuousOptimizing = true;
						<>1__state = -3;
						<maxMs>5__2 = PluginConfig.ContinuousOptimizationMaxMsPerFrame.Value;
						bool includeUnusedTextureScan = PluginConfig.ContinuousOptimizationIncludeUnusedTextureScan.Value || PluginConfig.UnloadUnusedTextures.Value;
						if (PluginConfig.EnableTextureOptimization.Value)
						{
							<>2__current = TextureOptimizer.OptimizeAllTexturesAggressiveCoroutine(includeUnusedTextureScan, <maxMs>5__2);
							<>1__state = 1;
							return true;
						}
						goto IL_00ac;
					}
					case 1:
						<>1__state = -3;
						goto IL_00ac;
					case 2:
						{
							<>1__state = -3;
							break;
						}
						IL_00ac:
						if (PluginConfig.EnableAudioOptimization.Value)
						{
							<>2__current = AudioOptimizer.OptimizeAllLoadedAudioClipsCoroutine(<maxMs>5__2);
							<>1__state = 2;
							return true;
						}
						break;
					}
					AssetOptimizer.OptimizeAllAssets();
					PerformLightCleanup();
					<>m__Finally1();
					return false;
				}
				catch
				{
					//try-fault
					((IDisposable)this).Dispose();
					throw;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			private void <>m__Finally1()
			{
				<>1__state = -1;
				_isContinuousOptimizing = false;
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[CompilerGenerated]
		private sealed class <PerformFullCleanupCoroutine>d__4 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <PerformFullCleanupCoroutine>d__4(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					if (_isCleaningUp)
					{
						return false;
					}
					_isCleaningUp = true;
					lc_memsaver.Logger.LogDebug((object)"Full cleanup coroutine starting...");
					MaterialOptimizationPatch.SuppressLogging = true;
					if (PluginConfig.EnableTextureOptimization.Value)
					{
						TextureOptimizer.OptimizeAllTexturesAggressive();
						<>2__current = null;
						<>1__state = 1;
						return true;
					}
					goto IL_007e;
				case 1:
					<>1__state = -1;
					goto IL_007e;
				case 2:
					<>1__state = -1;
					goto IL_00a6;
				case 3:
				{
					<>1__state = -1;
					MaterialOptimizationPatch.SuppressLogging = false;
					AsyncOperation val = Resources.UnloadUnusedAssets();
					<>2__current = val;
					<>1__state = 4;
					return true;
				}
				case 4:
					<>1__state = -1;
					GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
					GC.WaitForPendingFinalizers();
					<>2__current = null;
					<>1__state = 5;
					return true;
				case 5:
					<>1__state = -1;
					GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
					<>2__current = null;
					<>1__state = 6;
					return true;
				case 6:
					{
						<>1__state = -1;
						GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
						GC.Collect();
						lc_memsaver.Logger.LogDebug((object)"Full cleanup coroutine done.");
						_isCleaningUp = false;
						return false;
					}
					IL_007e:
					if (PluginConfig.EnableAudioOptimization.Value)
					{
						AudioOptimizer.OptimizeAllLoadedAudioClips();
						<>2__current = null;
						<>1__state = 2;
						return true;
					}
					goto IL_00a6;
					IL_00a6:
					AssetOptimizer.OptimizeAllAssets();
					<>2__current = null;
					<>1__state = 3;
					return true;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		private static bool _isCleaningUp;

		private static bool _isContinuousOptimizing;

		public static void PerformLightCleanup()
		{
			if (!_isCleaningUp)
			{
				lc_memsaver.Logger.LogDebug((object)"Light cleanup...");
				GC.Collect(0, GCCollectionMode.Optimized);
			}
		}

		public static void PerformFullCleanup()
		{
			if (_isCleaningUp)
			{
				return;
			}
			_isCleaningUp = true;
			lc_memsaver.Logger.LogDebug((object)"Full cleanup starting...");
			try
			{
				Resources.UnloadUnusedAssets();
				GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
				GC.WaitForPendingFinalizers();
				GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
				GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
				GC.Collect();
				lc_memsaver.Logger.LogDebug((object)"Full cleanup done.");
			}
			catch (Exception arg)
			{
				lc_memsaver.Logger.LogError((object)$"Error during full cleanup: {arg}");
			}
			finally
			{
				_isCleaningUp = false;
			}
		}

		[IteratorStateMachine(typeof(<PerformFullCleanupCoroutine>d__4))]
		public static IEnumerator PerformFullCleanupCoroutine()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <PerformFullCleanupCoroutine>d__4(0);
		}

		[IteratorStateMachine(typeof(<PerformContinuousOptimizationCoroutine>d__5))]
		public static IEnumerator PerformContinuousOptimizationCoroutine()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <PerformContinuousOptimizationCoroutine>d__5(0);
		}

		public static void ApplyGlobalTextureOptimizations()
		{
			if (PluginConfig.EnableTextureOptimization.Value)
			{
				int value = PluginConfig.GlobalTextureMipmapLimit.Value;
				if (value > 0)
				{
					QualitySettings.globalTextureMipmapLimit = value;
					lc_memsaver.Logger.LogInfo((object)$"Global mipmap limit: {value} (1/{1 << value} res, ~{GetEstimatedMipmapSavingsPercent(value)}% savings).");
				}
				if (PluginConfig.EnableMipmapStreaming.Value)
				{
					QualitySettings.streamingMipmapsActive = true;
					QualitySettings.streamingMipmapsMemoryBudget = PluginConfig.StreamingMipmapsBudgetMB.Value;
					QualitySettings.streamingMipmapsMaxLevelReduction = 2;
					Texture.streamingTextureDiscardUnusedMips = true;
					lc_memsaver.Logger.LogInfo((object)$"Mipmap streaming: {PluginConfig.StreamingMipmapsBudgetMB.Value}MB budget.");
				}
			}
		}

		private static int GetEstimatedMipmapSavingsPercent(int level)
		{
			return level switch
			{
				1 => 75, 
				2 => 93, 
				3 => 98, 
				_ => 0, 
			};
		}
	}
	public static class MeshOptimizer
	{
		public static void OptimizeAllLoadedMeshes()
		{
		}
	}
	public static class TextureOptimizer
	{
		[CompilerGenerated]
		private sealed class <OptimizeAllTexturesAggressiveCoroutine>d__2 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public int maxMsPerFrame;

			public bool includeUnusedTextureScan;

			private Texture2D[] <allTextures>5__2;

			private int <optimizedCount>5__3;

			private int <unloadedCount>5__4;

			private int <minTuneSize>5__5;

			private int <minUnusedForceSize>5__6;

			private HashSet<Texture> <usedTextures>5__7;

			private float <mipmapBias>5__8;

			private Stopwatch <sw>5__9;

			private Stopwatch <scanSw>5__10;

			private Renderer[] <>7__wrap10;

			private int <>7__wrap11;

			private RawImage[] <>7__wrap12;

			private Image[] <>7__wrap13;

			private SpriteRenderer[] <>7__wrap14;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <OptimizeAllTexturesAggressiveCoroutine>d__2(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<allTextures>5__2 = null;
				<usedTextures>5__7 = null;
				<sw>5__9 = null;
				<scanSw>5__10 = null;
				<>7__wrap10 = null;
				<>7__wrap12 = null;
				<>7__wrap13 = null;
				<>7__wrap14 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_040e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0427: Unknown result type (might be due to invalid IL or missing references)
				RawImage[] array2;
				Image[] array3;
				SpriteRenderer[] array4;
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					if (!PluginConfig.EnableTextureOptimization.Value)
					{
						return false;
					}
					if (maxMsPerFrame < 1)
					{
						maxMsPerFrame = 1;
					}
					<allTextures>5__2 = Resources.FindObjectsOfTypeAll<Texture2D>();
					<optimizedCount>5__3 = 0;
					<unloadedCount>5__4 = 0;
					<minTuneSize>5__5 = PluginConfig.MinTextureSizeForTuning.Value;
					<minUnusedForceSize>5__6 = PluginConfig.MinTextureSizeForUnusedForce.Value;
					<usedTextures>5__7 = null;
					if (includeUnusedTextureScan && PluginConfig.UnloadUnusedTextures.Value)
					{
						<usedTextures>5__7 = new HashSet<Texture>();
						Renderer[] array = Object.FindObjectsOfType<Renderer>();
						<scanSw>5__10 = Stopwatch.StartNew();
						<>7__wrap10 = array;
						<>7__wrap11 = 0;
						goto IL_016c;
					}
					goto IL_03c8;
				case 1:
					<>1__state = -1;
					goto IL_015e;
				case 2:
					<>1__state = -1;
					goto IL_020d;
				case 3:
					<>1__state = -1;
					goto IL_02d2;
				case 4:
					<>1__state = -1;
					goto IL_0399;
				case 5:
					{
						<>1__state = -1;
						goto IL_0679;
					}
					IL_020d:
					<>7__wrap11++;
					goto IL_021b;
					IL_015e:
					<>7__wrap11++;
					goto IL_016c;
					IL_0643:
					if (<sw>5__9.ElapsedMilliseconds >= maxMsPerFrame)
					{
						<sw>5__9.Restart();
						<>2__current = null;
						<>1__state = 5;
						return true;
					}
					goto IL_0679;
					IL_03c8:
					<mipmapBias>5__8 = PluginConfig.AggressiveMipmapBias.Value;
					<sw>5__9 = Stopwatch.StartNew();
					<>7__wrap11 = 0;
					goto IL_068b;
					IL_068b:
					if (<>7__wrap11 < <allTextures>5__2.Length)
					{
						Texture2D val = <allTextures>5__2[<>7__wrap11];
						if (!((Object)(object)val == (Object)null))
						{
							try
							{
								if ((!((Enum)((Object)val).hideFlags).HasFlag((Enum)(object)(HideFlags)8) || !((Enum)((Object)val).hideFlags).HasFlag((Enum)(object)(HideFlags)16)) && (!PluginConfig.ProtectUiTextures.Value || !IsLikelyUiTexture(val)) && (<minTuneSize>5__5 <= 0 || ((Texture)val).width >= <minTuneSize>5__5 || ((Texture)val).height >= <minTuneSize>5__5))
								{
									if (<mipmapBias>5__8 > 0f && ((Texture)val).mipmapCount > 1 && ((Texture)val).mipMapBias < <mipmapBias>5__8)
									{
										((Texture)val).mipMapBias = <mipmapBias>5__8;
										<optimizedCount>5__3++;
									}
									if (val.streamingMipmaps && ((Texture)val).mipmapCount > 2)
									{
										val.requestedMipmapLevel = Math.Min(((Texture)val).mipmapCount - 1, 2);
										<optimizedCount>5__3++;
									}
									if (<usedTextures>5__7 != null && PluginConfig.UnloadUnusedTextures.Value && ((Texture)val).width >= <minUnusedForceSize>5__6 && ((Texture)val).height >= <minUnusedForceSize>5__6 && !<usedTextures>5__7.Contains((Texture)(object)val))
									{
										string text = ((Object)val).name.ToLowerInvariant();
										if (!text.Contains("font") && !text.Contains("cursor") && !text.Contains("ui") && !text.Contains("icon") && !text.Contains("default") && !text.Contains("white") && !text.Contains("black") && !text.Contains("normal"))
										{
											if (((Texture)val).mipmapCount > 1)
											{
												val.requestedMipmapLevel = ((Texture)val).mipmapCount - 1;
												((Texture)val).mipMapBias = 3f;
											}
											<unloadedCount>5__4++;
										}
									}
									goto IL_0643;
								}
							}
							catch (Exception ex)
							{
								lc_memsaver.Logger.LogDebug((object)("Aggressive texture opt error on '" + ((Object)val).name + "': " + ex.Message));
								goto IL_0643;
							}
						}
						goto IL_0679;
					}
					lc_memsaver.Logger.LogDebug((object)$"[Continuous] Textures: {<optimizedCount>5__3} tuned, {<unloadedCount>5__4} forced low mip ({<allTextures>5__2.Length} total).");
					return false;
					IL_016c:
					if (<>7__wrap11 < <>7__wrap10.Length)
					{
						Renderer val2 = <>7__wrap10[<>7__wrap11];
						if (!((Object)(object)val2 == (Object)null))
						{
							Material[] sharedMaterials = val2.sharedMaterials;
							foreach (Material val3 in sharedMaterials)
							{
								if (!((Object)(object)val3 == (Object)null))
								{
									CollectMaterialTextures(val3, <usedTextures>5__7);
								}
							}
							if (<scanSw>5__10.ElapsedMilliseconds >= maxMsPerFrame)
							{
								<scanSw>5__10.Restart();
								<>2__current = null;
								<>1__state = 1;
								return true;
							}
						}
						goto IL_015e;
					}
					<>7__wrap10 = null;
					array2 = Object.FindObjectsOfType<RawImage>();
					<>7__wrap12 = array2;
					<>7__wrap11 = 0;
					goto IL_021b;
					IL_0679:
					<>7__wrap11++;
					goto IL_068b;
					IL_021b:
					if (<>7__wrap11 < <>7__wrap12.Length)
					{
						RawImage val4 = <>7__wrap12[<>7__wrap11];
						if ((Object)(object)val4 != (Object)null && (Object)(object)val4.texture != (Object)null)
						{
							<usedTextures>5__7.Add(val4.texture);
						}
						if (<scanSw>5__10.ElapsedMilliseconds >= maxMsPerFrame)
						{
							<scanSw>5__10.Restart();
							<>2__current = null;
							<>1__state = 2;
							return true;
						}
						goto IL_020d;
					}
					<>7__wrap12 = null;
					array3 = Object.FindObjectsOfType<Image>();
					<>7__wrap13 = array3;
					<>7__wrap11 = 0;
					goto IL_02e0;
					IL_0399:
					<>7__wrap11++;
					goto IL_03a7;
					IL_02e0:
					if (<>7__wrap11 < <>7__wrap13.Length)
					{
						Image val5 = <>7__wrap13[<>7__wrap11];
						if (!((Object)(object)val5 == (Object)null))
						{
							Sprite sprite = val5.sprite;
							if ((Object)(object)sprite != (Object)null && (Object)(object)sprite.texture != (Object)null)
							{
								<usedTextures>5__7.Add((Texture)(object)sprite.texture);
							}
							if (<scanSw>5__10.ElapsedMilliseconds >= maxMsPerFrame)
							{
								<scanSw>5__10.Restart();
								<>2__current = null;
								<>1__state = 3;
								return true;
							}
						}
						goto IL_02d2;
					}
					<>7__wrap13 = null;
					array4 = Object.FindObjectsOfType<SpriteRenderer>();
					<>7__wrap14 = array4;
					<>7__wrap11 = 0;
					goto IL_03a7;
					IL_02d2:
					<>7__wrap11++;
					goto IL_02e0;
					IL_03a7:
					if (<>7__wrap11 < <>7__wrap14.Length)
					{
						SpriteRenderer val6 = <>7__wrap14[<>7__wrap11];
						if (!((Object)(object)val6 == (Object)null))
						{
							Sprite sprite2 = val6.sprite;
							if ((Object)(object)sprite2 != (Object)null && (Object)(object)sprite2.texture != (Object)null)
							{
								<usedTextures>5__7.Add((Texture)(object)sprite2.texture);
							}
							if (<scanSw>5__10.ElapsedMilliseconds >= maxMsPerFrame)
							{
								<scanSw>5__10.Restart();
								<>2__current = null;
								<>1__state = 4;
								return true;
							}
						}
						goto IL_0399;
					}
					<>7__wrap14 = null;
					<scanSw>5__10 = null;
					goto IL_03c8;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		private static bool IsLikelyUiTexture(Texture2D texture)
		{
			if ((Object)(object)texture == (Object)null)
			{
				return true;
			}
			if (string.IsNullOrWhiteSpace(((Object)texture).name))
			{
				return true;
			}
			string name = ((Object)texture).name;
			if (name.IndexOf("font", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return true;
			}
			if (name.IndexOf("cursor", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return true;
			}
			if (name.IndexOf("ui", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return true;
			}
			if (name.IndexOf("icon", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return true;
			}
			if (name.IndexOf("sprite", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return true;
			}
			if (name.IndexOf("button", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return true;
			}
			if (name.IndexOf("hud", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return true;
			}
			return false;
		}

		public static void OptimizeAllTexturesAggressive()
		{
			//IL_01d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ee: Unknown result type (might be due to invalid IL or missing references)
			if (!PluginConfig.EnableTextureOptimization.Value)
			{
				return;
			}
			Texture2D[] array = Resources.FindObjectsOfTypeAll<Texture2D>();
			int num = 0;
			int num2 = 0;
			int value = PluginConfig.MinTextureSizeForTuning.Value;
			int value2 = PluginConfig.MinTextureSizeForUnusedForce.Value;
			HashSet<Texture> hashSet = new HashSet<Texture>();
			Renderer[] array2 = Object.FindObjectsOfType<Renderer>();
			Renderer[] array3 = array2;
			foreach (Renderer val in array3)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				Material[] sharedMaterials = val.sharedMaterials;
				foreach (Material val2 in sharedMaterials)
				{
					if (!((Object)(object)val2 == (Object)null))
					{
						CollectMaterialTextures(val2, hashSet);
					}
				}
			}
			RawImage[] array4 = Object.FindObjectsOfType<RawImage>();
			RawImage[] array5 = array4;
			foreach (RawImage val3 in array5)
			{
				if ((Object)(object)val3 != (Object)null && (Object)(object)val3.texture != (Object)null)
				{
					hashSet.Add(val3.texture);
				}
			}
			Image[] array6 = Object.FindObjectsOfType<Image>();
			Image[] array7 = array6;
			foreach (Image val4 in array7)
			{
				if (!((Object)(object)val4 == (Object)null))
				{
					Sprite sprite = val4.sprite;
					if ((Object)(object)sprite != (Object)null && (Object)(object)sprite.texture != (Object)null)
					{
						hashSet.Add((Texture)(object)sprite.texture);
					}
				}
			}
			SpriteRenderer[] array8 = Object.FindObjectsOfType<SpriteRenderer>();
			SpriteRenderer[] array9 = array8;
			foreach (SpriteRenderer val5 in array9)
			{
				if (!((Object)(object)val5 == (Object)null))
				{
					Sprite sprite2 = val5.sprite;
					if ((Object)(object)sprite2 != (Object)null && (Object)(object)sprite2.texture != (Object)null)
					{
						hashSet.Add((Texture)(object)sprite2.texture);
					}
				}
			}
			float value3 = PluginConfig.AggressiveMipmapBias.Value;
			Texture2D[] array10 = array;
			foreach (Texture2D val6 in array10)
			{
				if ((Object)(object)val6 == (Object)null)
				{
					continue;
				}
				try
				{
					if ((((Enum)((Object)val6).hideFlags).HasFlag((Enum)(object)(HideFlags)8) && ((Enum)((Object)val6).hideFlags).HasFlag((Enum)(object)(HideFlags)16)) || (PluginConfig.ProtectUiTextures.Value && IsLikelyUiTexture(val6)) || (value > 0 && ((Texture)val6).width < value && ((Texture)val6).height < value))
					{
						continue;
					}
					if (value3 > 0f && ((Texture)val6).mipmapCount > 1 && ((Texture)val6).mipMapBias < value3)
					{
						((Texture)val6).mipMapBias = value3;
						num++;
					}
					if (val6.streamingMipmaps && ((Texture)val6).mipmapCount > 2)
					{
						val6.requestedMipmapLevel = Math.Min(((Texture)val6).mipmapCount - 1, 2);
						num++;
					}
					if (!PluginConfig.UnloadUnusedTextures.Value || ((Texture)val6).width < value2 || ((Texture)val6).height < value2 || hashSet.Contains((Texture)(object)val6))
					{
						continue;
					}
					string text = ((Object)val6).name.ToLowerInvariant();
					if (!text.Contains("font") && !text.Contains("cursor") && !text.Contains("ui") && !text.Contains("icon") && !text.Contains("default") && !text.Contains("white") && !text.Contains("black") && !text.Contains("normal"))
					{
						if (((Texture)val6).mipmapCount > 1)
						{
							val6.requestedMipmapLevel = ((Texture)val6).mipmapCount - 1;
							((Texture)val6).mipMapBias = 3f;
						}
						num2++;
					}
				}
				catch (Exception ex)
				{
					lc_memsaver.Logger.LogDebug((object)("Aggressive texture opt error on '" + ((Object)val6).name + "': " + ex.Message));
				}
			}
			lc_memsaver.Logger.LogInfo((object)$"Textures (aggressive): {num} tuned, {num2} forced low mip ({array.Length} total).");
		}

		[IteratorStateMachine(typeof(<OptimizeAllTexturesAggressiveCoroutine>d__2))]
		public static IEnumerator OptimizeAllTexturesAggressiveCoroutine(bool includeUnusedTextureScan, int maxMsPerFrame)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <OptimizeAllTexturesAggressiveCoroutine>d__2(0)
			{
				includeUnusedTextureScan = includeUnusedTextureScan,
				maxMsPerFrame = maxMsPerFrame
			};
		}

		private static void CollectMaterialTextures(Material mat, HashSet<Texture> set)
		{
			int[] array = new int[13]
			{
				Shader.PropertyToID("_MainTex"),
				Shader.PropertyToID("_BumpMap"),
				Shader.PropertyToID("_MetallicGlossMap"),
				Shader.PropertyToID("_OcclusionMap"),
				Shader.PropertyToID("_EmissionMap"),
				Shader.PropertyToID("_DetailAlbedoMap"),
				Shader.PropertyToID("_DetailNormalMap"),
				Shader.PropertyToID("_ParallaxMap"),
				Shader.PropertyToID("_SpecGlossMap"),
				Shader.PropertyToID("_BaseMap"),
				Shader.PropertyToID("_BaseColorMap"),
				Shader.PropertyToID("_NormalMap"),
				Shader.PropertyToID("_MaskMap")
			};
			int[] array2 = array;
			foreach (int num in array2)
			{
				if (mat.HasProperty(num))
				{
					Texture texture = mat.GetTexture(num);
					if ((Object)(object)texture != (Object)null)
					{
						set.Add(texture);
					}
				}
			}
		}

		private static long EstimateTextureMemory(Texture2D texture)
		{
			//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_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Invalid comparison between Unknown and I4
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Invalid comparison between Unknown and I4
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected I4, but got Unknown
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Invalid comparison between Unknown and I4
			//IL_0020: 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_003d: Expected I4, but got Unknown
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Invalid comparison between Unknown and I4
			TextureFormat format = texture.format;
			int num;
			if ((int)format <= 14)
			{
				switch (format - 3)
				{
				case 1:
					goto IL_0050;
				case 2:
					goto IL_0055;
				case 0:
					goto IL_005a;
				}
				switch (format - 10)
				{
				case 4:
					break;
				case 0:
					goto IL_0064;
				case 2:
					goto IL_0068;
				default:
					goto IL_0078;
				}
				num = 32;
			}
			else if ((int)format != 25)
			{
				if ((int)format != 47)
				{
					if ((int)format != 48)
					{
						goto IL_0078;
					}
					num = 8;
				}
				else
				{
					num = 8;
				}
			}
			else
			{
				num = 8;
			}
			goto IL_007b;
			IL_005a:
			num = 24;
			goto IL_007b;
			IL_0078:
			num = 16;
			goto IL_007b;
			IL_007b:
			int num2 = num;
			long num3 = (long)((Texture)texture).width * (long)((Texture)texture).height * num2 / 8;
			if (((Texture)texture).mipmapCount > 1)
			{
				num3 = (long)((float)num3 * 1.33f);
			}
			return num3;
			IL_0068:
			num = 8;
			goto IL_007b;
			IL_0064:
			num = 4;
			goto IL_007b;
			IL_0055:
			num = 32;
			goto IL_007b;
			IL_0050:
			num = 32;
			goto IL_007b;
		}
	}
}
namespace lc_memsaver.Config
{
	public static class PluginConfig
	{
		public static ConfigEntry<bool> EnableMemoryLogging { get; private set; }

		public static ConfigEntry<bool> EnableTextureOptimization { get; private set; }

		public static ConfigEntry<bool> EnableMipmapStreaming { get; private set; }

		public static ConfigEntry<int> GlobalTextureMipmapLimit { get; private set; }

		public static ConfigEntry<float> StreamingMipmapsBudgetMB { get; private set; }

		public static ConfigEntry<bool> ProtectUiTextures { get; private set; }

		public static ConfigEntry<int> MinTextureSizeForTuning { get; private set; }

		public static ConfigEntry<int> MinTextureSizeForUnusedForce { get; private set; }

		public static ConfigEntry<bool> EnableAudioOptimization { get; private set; }

		public static ConfigEntry<float> AudioStreamingThresholdSeconds { get; private set; }

		public static ConfigEntry<bool> ForceMonoAudio { get; private set; }

		public static ConfigEntry<bool> AggressiveAudioUnload { get; private set; }

		public static ConfigEntry<int> MaxAudioClipsUnloadedPerPass { get; private set; }

		public static ConfigEntry<bool> ProtectReferencedAudioClips { get; private set; }

		public static ConfigEntry<bool> PreloadReferencedAudioOnSceneLoad { get; private set; }

		public static ConfigEntry<int> MaxReferencedAudioPreloadsPerPass { get; private set; }

		public static ConfigEntry<bool> ProtectOneShotAudioClips { get; private set; }

		public static ConfigEntry<float> ProtectRecentlyUsedAudioSeconds { get; private set; }

		public static ConfigEntry<bool> EnablePeriodicGC { get; private set; }

		public static ConfigEntry<float> PeriodicGCIntervalSeconds { get; private set; }

		public static ConfigEntry<bool> EnableAggressiveLevelTransitionCleanup { get; private set; }

		public static ConfigEntry<float> AggressiveMipmapBias { get; private set; }

		public static ConfigEntry<bool> UnloadUnusedTextures { get; private set; }

		public static ConfigEntry<bool> EnableMaterialDeduplication { get; private set; }

		public static ConfigEntry<bool> PreventNonReadableDynamicMeshes { get; private set; }

		public static ConfigEntry<bool> PreventUnsafeRuntimeTextureCompress { get; private set; }

		public static ConfigEntry<bool> PreventNonReadableMeshesGlobally { get; private set; }

		public static ConfigEntry<bool> PreventGetTrianglesOnNonTriangleMeshes { get; private set; }

		public static ConfigEntry<bool> PreventVoiceChatAudioUnload { get; private set; }

		public static ConfigEntry<bool> SuppressSpawnMapObjectsExceptions { get; private set; }

		public static ConfigEntry<bool> EnableContinuousOptimization { get; private set; }

		public static ConfigEntry<float> ContinuousOptimizationIntervalSeconds { get; private set; }

		public static ConfigEntry<int> ContinuousOptimizationMaxMsPerFrame { get; private set; }

		public static ConfigEntry<bool> ContinuousOptimizationIncludeUnusedTextureScan { get; private set; }

		public static ConfigEntry<bool> EnableAssetBehaviorLearning { get; private set; }

		public static ConfigEntry<bool> PersistAssetBehaviorToDisk { get; private set; }

		public static ConfigEntry<float> AssetBehaviorSaveIntervalSeconds { get; private set; }

		public static void Initialize(ConfigFile config)
		{
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Expected O, but got Unknown
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Expected O, but got Unknown
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Expected O, but got Unknown
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: Expected O, but got Unknown
			//IL_0165: Unknown result type (might be due to invalid IL or missing references)
			//IL_016f: Expected O, but got Unknown
			//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dd: Expected O, but got Unknown
			//IL_023e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0248: Expected O, but got Unknown
			//IL_02a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b3: Expected O, but got Unknown
			//IL_02fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0306: Expected O, but got Unknown
			//IL_034f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0359: Expected O, but got Unknown
			//IL_047a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0484: Expected O, but got Unknown
			//IL_04a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_04b1: Expected O, but got Unknown
			//IL_0530: Unknown result type (might be due to invalid IL or missing references)
			//IL_053a: Expected O, but got Unknown
			EnableMemoryLogging = config.Bind<bool>("General", "EnableMemoryLogging", true, "Log memory usage statistics at key moments (useful for debugging).");
			EnableTextureOptimization = config.Bind<bool>("Texture Optimization", "EnableTextureOptimization", true, "Enable reversible texture optimizations (mipmap bias, aniso level, streaming hints).");
			EnableMipmapStreaming = config.Bind<bool>("Texture Optimization", "EnableMipmapStreaming", true, "Enable Unity mipmap streaming to load only the mip levels needed.");
			GlobalTextureMipmapLimit = config.Bind<int>("Texture Optimization", "GlobalTextureMipmapLimit", 0, new ConfigDescription("Force all textures to skip N mip levels (0 = full quality, 1 = half res, 2 = quarter res). This works on ALL textures including non-readable ones from mods. Huge memory savings.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 3), Array.Empty<object>()));
			StreamingMipmapsBudgetMB = config.Bind<float>("Texture Optimization", "StreamingMipmapsBudgetMB", 128f, new ConfigDescription("Memory budget in MB for mipmap streaming. Lower = less RAM but more texture pop-in.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(32f, 512f), Array.Empty<object>()));
			ProtectUiTextures = config.Bind<bool>("Texture Optimization", "ProtectUiTextures", true, "Skips textures that are likely UI/fonts/icons/cursors/sprites when applying mip bias or forcing low mips. Helps prevent broken HUD/UI.");
			MinTextureSizeForTuning = config.Bind<int>("Texture Optimization", "MinTextureSizeForTuning", 256, new ConfigDescription("Only tune textures at or above this size (in pixels). Smaller textures are usually UI/lookup and not worth touching.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 2048), Array.Empty<object>()));
			MinTextureSizeForUnusedForce = config.Bind<int>("Texture Optimization", "MinTextureSizeForUnusedForce", 1024, new ConfigDescription("Only force lowest mip on UNUSED textures at or above this size (in pixels).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(256, 4096), Array.Empty<object>()));
			AggressiveMipmapBias = config.Bind<float>("Texture Optimization", "AggressiveMipmapBias", 1f, new ConfigDescription("Mipmap bias applied to ALL textures (even non-readable). Higher = blurrier but much less VRAM. 0 = no change, 1 = prefer next lower mip, 2 = very aggressive.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>()));
			UnloadUnusedTextures = config.Bind<bool>("Texture Optimization", "UnloadUnusedTextures", true, "Force lowest mip level on large textures (>512x512) not referenced by any active renderer. Safe and effective - textures stream back to higher mips if needed later.");
			EnableAudioOptimization = config.Bind<bool>("Audio Optimization", "EnableAudioOptimization", true, "Enable audio clip memory optimization (streaming long clips, forcing mono).");
			AudioStreamingThresholdSeconds = config.Bind<float>("Audio Optimization", "AudioStreamingThresholdSeconds", 5f, new ConfigDescription("Audio clips longer than this (in seconds) will have their data unloaded to save RAM. They reload on demand.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 120f), Array.Empty<object>()));
			ForceMonoAudio = config.Bind<bool>("Audio Optimization", "ForceMonoAudio", false, "Force all audio to mono (halves audio memory). May reduce spatial audio quality.");
			AggressiveAudioUnload = config.Bind<bool>("Audio Optimization", "AggressiveAudioUnload", true, "Unload ALL audio clips not currently playing (not just long ones). Very effective but may cause brief audio pops on first play.");
			MaxAudioClipsUnloadedPerPass = config.Bind<int>("Audio Optimization", "MaxClipsUnloadedPerPass", 250, new ConfigDescription("Safety cap: maximum number of audio clips to unload per optimization pass. Lower = fewer hitches, slower memory recovery.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10, 2000), Array.Empty<object>()));
			ProtectReferencedAudioClips = config.Bind<bool>("Audio Optimization", "ProtectReferencedAudioClips", true, "If enabled, never unload AudioClips that are referenced by an AudioSource in the currently loaded scene(s). This prevents cases where unloading breaks playback for some mods/systems.");
			PreloadReferencedAudioOnSceneLoad = config.Bind<bool>("Audio Optimization", "PreloadReferencedAudioOnSceneLoad", true, "When a scene/level finishes loading, batch-load AudioClips referenced by AudioSources (helps avoid silent audio after optimizations).");
			MaxReferencedAudioPreloadsPerPass = config.Bind<int>("Audio Optimization", "MaxReferencedAudioPreloadsPerPass", 300, new ConfigDescription("Safety cap: maximum referenced AudioClips to preload per pass.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10, 5000), Array.Empty<object>()));
			ProtectOneShotAudioClips = config.Bind<bool>("Audio Optimization", "ProtectOneShotAudioClips", true, "Ensures AudioClips played via AudioSource.PlayOneShot(...) stay loaded (preloads data on demand and avoids unloading them). Fixes issues like missing footsteps after optimization.");
			ProtectRecentlyUsedAudioSeconds = config.Bind<float>("Audio Optimization", "ProtectRecentlyUsedAudioSeconds", 120f, new ConfigDescription("Do not unload AudioClips that were used recently (seconds). Higher = safer, lower = more memory savings.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 600f), Array.Empty<object>()));
			EnablePeriodicGC = config.Bind<bool>("GC / Cleanup", "EnablePeriodicGC", true, "Enable periodic garbage collection to prevent memory buildup.");
			PeriodicGCIntervalSeconds = config.Bind<float>("GC / Cleanup", "PeriodicGCIntervalSeconds", 120f, new ConfigDescription("Interval in seconds between periodic light GC passes.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(30f, 600f), Array.Empty<object>()));
			EnableAggressiveLevelTransitionCleanup = config.Bind<bool>("GC / Cleanup", "EnableAggressiveLevelTransitionCleanup", true, "Perform aggressive memory cleanup when transitioning between levels/moons.");
			EnableMaterialDeduplication = config.Bind<bool>("Asset Optimization", "EnableMaterialDeduplication", true, "Detect and replace instanced material copies with shared originals. Reduces memory from Renderer.material abuse by mods.");
			PreventNonReadableDynamicMeshes = config.Bind<bool>("Compatibility", "PreventNonReadableDynamicMeshes", true, "Prevents Mesh.UploadMeshData(true) from making unnamed/dynamic meshes non-readable. This avoids Unity UI/CanvasRenderer errors like 'mesh is not readable'. May reduce memory savings from other mods that aggressively strip mesh CPU data.");
			PreventNonReadableMeshesGlobally = config.Bind<bool>("Compatibility", "PreventNonReadableMeshesGlobally", true, "Forces Mesh.UploadMeshData(true) to behave like UploadMeshData(false) for ALL meshes. This is the safest option to prevent mods from breaking meshes/UI by stripping CPU vertex data. Will reduce memory savings from mesh CPU data stripping.");
			PreventGetTrianglesOnNonTriangleMeshes = config.Bind<bool>("Compatibility", "PreventGetTrianglesOnNonTriangleMeshes", true, "Prevents Unity error spam like 'Failed getting triangles. Submesh topology is lines or points' by returning empty triangles for non-triangle meshes.");
			PreventUnsafeRuntimeTextureCompress = config.Bind<bool>("Compatibility", "PreventUnsafeRuntimeTextureCompress", true, "Blocks Texture2D.Compress() when the texture has no readable data or dimensions aren't multiples of 4. Prevents Unity errors like 'There is no texture data available to compress' and 'Compress will not work'.");
			PreventVoiceChatAudioUnload = config.Bind<bool>("Compatibility", "PreventVoiceChatAudioUnload", true, "Prevents unloading AudioClips used for voice/microphone capture (e.g. Dissonance, Microphone, DontSave clips). Helps avoid mic restart loops like 'BasicMicrophoneCapture: zero length clip'.");
			SuppressSpawnMapObjectsExceptions = config.Bind<bool>("Compatibility", "SuppressSpawnMapObjectsExceptions", true, "If enabled, suppresses exceptions thrown during RoundManager.SpawnMapObjects(). Useful when a map-object mod (e.g., LethalLib/Dawn) throws NullReferenceException and breaks level loading. Side effect: the broken mod's map objects may not spawn for that run.");
			EnableContinuousOptimization = config.Bind<bool>("Continuous Optimization", "EnableContinuousOptimization", true, "Run small, non-blocking optimization passes at a fixed interval to reduce hitches.");
			ContinuousOptimizationIntervalSeconds = config.Bind<float>("Continuous Optimization", "IntervalSeconds", 60f, new ConfigDescription("How often to run continuous optimization passes.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 600f), Array.Empty<object>()));
			ContinuousOptimizationMaxMsPerFrame = config.Bind<int>("Continuous Optimization", "MaxMsPerFrame", 3, new ConfigDescription("Time budget per frame for batched optimizers (lower = less hitching, slower completion).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10), Array.Empty<object>()));
			ContinuousOptimizationIncludeUnusedTextureScan = config.Bind<bool>("Continuous Optimization", "IncludeUnusedTextureScan", true, "When enabled, continuous texture optimization will scan active renderers/materials to detect unused textures. This can be expensive; keep off if you want minimal hitching.");
			EnableAssetBehaviorLearning = config.Bind<bool>("Behavior Learning", "EnableAssetBehaviorLearning", true, "Learns which assets are frequently used and adjusts unloading behavior to prevent missing SFX (MVP: audio only).");
			PersistAssetBehaviorToDisk = config.Bind<bool>("Behavior Learning", "PersistAssetBehaviorToDisk", true, "Save learned asset behavior to disk so the mod can make better decisions across sessions.");
			AssetBehaviorSaveIntervalSeconds = config.Bind<float>("Behavior Learning", "SaveIntervalSeconds", 120f, new ConfigDescription("How often to save the behavior database (seconds).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(30f, 900f), Array.Empty<object>()));
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}