Decompiled source of lc memsaver v1.0.0

yutho.lc-memsaver.dll

Decompiled 17 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
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.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using LobbyCompatibility.Attributes;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.Profiling;
using UnityEngine.SceneManagement;
using lc_memsaver.Config;
using lc_memsaver.Managers;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("yutho.lc-memsaver")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("lc-memsaver")]
[assembly: AssemblyTitle("yutho.lc-memsaver")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.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("yutho.lc-memsaver", "lc-memsaver", "1.0.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[LobbyCompatibility(/*Could not decode attribute arguments.*/)]
	public class lc_memsaver : BaseUnityPlugin
	{
		[CompilerGenerated]
		private sealed class <RunInitialOptimization>d__16 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public lc_memsaver <>4__this;

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

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

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

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

			private bool MoveNext()
			{
				//IL_0034: Unknown result type (might be due to invalid IL or missing references)
				//IL_003e: Expected O, but got Unknown
				//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
				//IL_00bb: 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;
					Logger.LogInfo((object)"Running initial memory optimization pass...");
					LogMemoryUsage("Before Initial Optimization");
					if (PluginConfig.EnableTextureOptimization.Value)
					{
						TextureOptimizer.OptimizeAllLoadedTextures();
					}
					if (PluginConfig.EnableAudioOptimization.Value)
					{
						AudioOptimizer.OptimizeAllLoadedAudioClips();
					}
					if (PluginConfig.EnableMeshOptimization.Value)
					{
						MeshOptimizer.OptimizeAllLoadedMeshes();
					}
					MemoryManager.PerformFullCleanup();
					<>2__current = (object)new WaitForSeconds(2f);
					<>1__state = 2;
					return true;
				case 2:
					<>1__state = -1;
					LogMemoryUsage("After Initial Optimization");
					return false;
				}
			}

			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 bool _hasRunInitialOptimization;

		public static lc_memsaver Instance { get; private set; }

		internal static ManualLogSource Logger { get; private set; }

		internal static Harmony? Harmony { get; set; }

		private void Awake()
		{
			Logger = ((BaseUnityPlugin)this).Logger;
			Instance = this;
			PluginConfig.Initialize(((BaseUnityPlugin)this).Config);
			Patch();
			Logger.LogInfo((object)"yutho.lc-memsaver v1.0.0 has loaded!");
			Logger.LogInfo((object)"Memory optimization systems initialized.");
			LogMemoryUsage("Plugin Startup");
		}

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

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

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

		internal static void Patch()
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Expected O, but got Unknown
			if (Harmony == null)
			{
				Harmony = new Harmony("yutho.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 = "yutho.lc-memsaver";

		public const string PLUGIN_NAME = "lc-memsaver";

		public const string PLUGIN_VERSION = "1.0.0";
	}
}
namespace lc_memsaver.Patches
{
	[HarmonyPatch]
	public class MaterialOptimizationPatch
	{
		private static readonly Dictionary<int, Material> SharedMaterialCache = new Dictionary<int, Material>();

		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		[HarmonyPrefix]
		private static void OnGetMaterial(Renderer __instance)
		{
			if (PluginConfig.EnableMemoryLogging.Value)
			{
				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(RoundManager))]
	public class RoundManagerPatch
	{
		[HarmonyPatch("LoadNewLevel")]
		[HarmonyPrefix]
		private static void OnLoadNewLevelPrefix(RoundManager __instance)
		{
			if (PluginConfig.EnableAggressiveLevelTransitionCleanup.Value)
			{
				lc_memsaver.Logger.LogInfo((object)"Loading new level - pre-cleanup to free previous level memory...");
				lc_memsaver.LogMemoryUsage("Before Level Load Cleanup");
				MemoryManager.PerformFullCleanup();
			}
		}

		[HarmonyPatch("FinishGeneratingNewLevelClientRpc")]
		[HarmonyPostfix]
		private static void OnFinishGeneratingPostfix(RoundManager __instance)
		{
			lc_memsaver.Logger.LogInfo((object)"Level generation complete - optimizing new assets...");
			if (PluginConfig.EnableTextureOptimization.Value)
			{
				TextureOptimizer.OptimizeAllLoadedTextures();
			}
			if (PluginConfig.EnableMeshOptimization.Value)
			{
				MeshOptimizer.OptimizeAllLoadedMeshes();
			}
			if (PluginConfig.EnableAudioOptimization.Value)
			{
				AudioOptimizer.PreloadCriticalAudio();
			}
			MemoryManager.PerformLightCleanup();
			lc_memsaver.LogMemoryUsage("After Level Generation Optimization");
		}

		[HarmonyPatch("UnloadSceneObjectsEarly")]
		[HarmonyPostfix]
		private static void OnUnloadScenePostfix()
		{
			if (PluginConfig.EnableAggressiveLevelTransitionCleanup.Value)
			{
				lc_memsaver.Logger.LogInfo((object)"Scene objects unloading - performing memory cleanup...");
				MemoryManager.PerformFullCleanup();
			}
		}

		[HarmonyPatch("GenerateNewFloor")]
		[HarmonyPostfix]
		private static void OnGenerateNewFloorPostfix()
		{
			lc_memsaver.Logger.LogInfo((object)"Dungeon floor generated - optimizing interior assets...");
			if (PluginConfig.EnableTextureOptimization.Value)
			{
				TextureOptimizer.OptimizeAllLoadedTextures();
			}
			if (PluginConfig.EnableMeshOptimization.Value)
			{
				MeshOptimizer.OptimizeAllLoadedMeshes();
			}
			MemoryManager.PerformLightCleanup();
		}
	}
	[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.LogInfo((object)("Scene '" + ((Scene)(ref scene)).name + "' unloaded - cleaning memory..."));
				lc_memsaver.LogMemoryUsage("After Unloading '" + ((Scene)(ref scene)).name + "'");
				MemoryManager.PerformLightCleanup();
			}
		}

		private static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			//IL_0012: 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_0025: Invalid comparison between Unknown and I4
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Invalid comparison between Unknown and I4
			lc_memsaver.Logger.LogInfo((object)$"Scene '{((Scene)(ref scene)).name}' loaded (mode: {mode}) - optimizing assets...");
			if ((int)mode == 1)
			{
				if (PluginConfig.EnableTextureOptimization.Value)
				{
					TextureOptimizer.OptimizeAllLoadedTextures();
				}
				if (PluginConfig.EnableMeshOptimization.Value)
				{
					MeshOptimizer.OptimizeAllLoadedMeshes();
				}
			}
			if ((int)mode == 0)
			{
				MemoryManager.PerformFullCleanup();
			}
			lc_memsaver.LogMemoryUsage("After Loading '" + ((Scene)(ref scene)).name + "'");
		}
	}
	[HarmonyPatch(typeof(StartOfRound))]
	public class StartOfRoundPatch
	{
		[HarmonyPatch("Start")]
		[HarmonyPostfix]
		private static void OnStartPostfix(StartOfRound __instance)
		{
			lc_memsaver.Logger.LogInfo((object)"Game session starting - applying memory optimizations...");
			MemoryManager.ApplyRenderOptimizations();
		}

		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		[HarmonyPostfix]
		private static void OnOpeningDoorsPostfix()
		{
			if (PluginConfig.EnableAggressiveLevelTransitionCleanup.Value)
			{
				lc_memsaver.Logger.LogInfo((object)"Landed on moon - performing memory cleanup...");
				MemoryManager.PerformLightCleanup();
			}
		}

		[HarmonyPatch("ShipLeave")]
		[HarmonyPostfix]
		private static void OnShipLeavePostfix()
		{
			if (PluginConfig.EnableAggressiveLevelTransitionCleanup.Value)
			{
				lc_memsaver.Logger.LogInfo((object)"Ship leaving moon - performing aggressive memory cleanup...");
				((MonoBehaviour)lc_memsaver.Instance).StartCoroutine(MemoryManager.PerformFullCleanupCoroutine());
			}
		}

		[HarmonyPatch("EndOfGame")]
		[HarmonyPostfix]
		private static void OnEndOfGamePostfix()
		{
			if (PluginConfig.EnableAggressiveLevelTransitionCleanup.Value)
			{
				lc_memsaver.Logger.LogInfo((object)"End of game round - performing memory cleanup...");
				MemoryManager.PerformFullCleanup();
			}
		}
	}
}
namespace lc_memsaver.Managers
{
	public static class AudioOptimizer
	{
		private static int _totalClipsOptimized;

		private static long _estimatedBytesSaved;

		public static void OptimizeAllLoadedAudioClips()
		{
			if (!PluginConfig.EnableAudioOptimization.Value)
			{
				return;
			}
			_totalClipsOptimized = 0;
			_estimatedBytesSaved = 0L;
			AudioClip[] array = Resources.FindObjectsOfTypeAll<AudioClip>();
			lc_memsaver.Logger.LogInfo((object)$"Scanning {array.Length} loaded audio clips for optimization...");
			AudioClip[] array2 = array;
			foreach (AudioClip val in array2)
			{
				try
				{
					OptimizeAudioClip(val);
				}
				catch (Exception ex)
				{
					lc_memsaver.Logger.LogDebug((object)("Could not optimize audio clip '" + ((Object)val).name + "': " + ex.Message));
				}
			}
			lc_memsaver.Logger.LogInfo((object)($"Audio optimization complete: {_totalClipsOptimized} clips optimized, " + $"~{_estimatedBytesSaved / 1048576}MB estimated savings."));
		}

		private static void OptimizeAudioClip(AudioClip clip)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Invalid comparison between Unknown and I4
			if (!((Object)(object)clip == (Object)null))
			{
				long num = EstimateAudioMemory(clip);
				if (clip.length >= PluginConfig.AudioStreamingThresholdSeconds.Value && (int)clip.loadState == 2 && !IsClipCurrentlyPlaying(clip))
				{
					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 bool IsClipCurrentlyPlaying(AudioClip clip)
		{
			AudioSource[] array = Object.FindObjectsOfType<AudioSource>();
			AudioSource[] array2 = array;
			foreach (AudioSource val in array2)
			{
				if (val.isPlaying && (Object)(object)val.clip == (Object)(object)clip)
				{
					return true;
				}
			}
			return false;
		}

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

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

			private object <>2__current;

			private AsyncOperation <unloadOp>5__1;

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

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

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

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<unloadOp>5__1 = null;
				<>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.LogInfo((object)"Performing full memory cleanup (coroutine)...");
					lc_memsaver.LogMemoryUsage("Before Full Cleanup (Coroutine)");
					if (PluginConfig.EnableTextureOptimization.Value)
					{
						TextureOptimizer.OptimizeAllLoadedTextures();
						<>2__current = null;
						<>1__state = 1;
						return true;
					}
					goto IL_00ab;
				case 1:
					<>1__state = -1;
					goto IL_00ab;
				case 2:
					<>1__state = -1;
					goto IL_00d8;
				case 3:
					<>1__state = -1;
					goto IL_0107;
				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.LogMemoryUsage("After Full Cleanup (Coroutine)");
						lc_memsaver.Logger.LogInfo((object)"Full memory cleanup (coroutine) complete.");
						_isCleaningUp = false;
						return false;
					}
					IL_0107:
					<unloadOp>5__1 = Resources.UnloadUnusedAssets();
					<>2__current = <unloadOp>5__1;
					<>1__state = 4;
					return true;
					IL_00d8:
					if (PluginConfig.EnableMeshOptimization.Value)
					{
						MeshOptimizer.OptimizeAllLoadedMeshes();
						<>2__current = null;
						<>1__state = 3;
						return true;
					}
					goto IL_0107;
					IL_00ab:
					if (PluginConfig.EnableAudioOptimization.Value)
					{
						AudioOptimizer.OptimizeAllLoadedAudioClips();
						<>2__current = null;
						<>1__state = 2;
						return true;
					}
					goto IL_00d8;
				}
			}

			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;

		public static void PerformLightCleanup()
		{
			if (!_isCleaningUp)
			{
				lc_memsaver.Logger.LogDebug((object)"Performing light memory cleanup...");
				lc_memsaver.LogMemoryUsage("Before Light Cleanup");
				Resources.UnloadUnusedAssets();
				GC.Collect(0, GCCollectionMode.Optimized);
				lc_memsaver.Logger.LogDebug((object)"Light cleanup complete.");
			}
		}

		public static void PerformFullCleanup()
		{
			if (_isCleaningUp)
			{
				return;
			}
			_isCleaningUp = true;
			lc_memsaver.Logger.LogInfo((object)"Performing full memory cleanup...");
			lc_memsaver.LogMemoryUsage("Before Full Cleanup");
			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.LogMemoryUsage("After Full Cleanup");
				lc_memsaver.Logger.LogInfo((object)"Full memory cleanup complete.");
			}
			catch (Exception arg)
			{
				lc_memsaver.Logger.LogError((object)$"Error during full cleanup: {arg}");
			}
			finally
			{
				_isCleaningUp = false;
			}
		}

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

		public static void ApplyRenderOptimizations()
		{
			if (PluginConfig.EnableRenderOptimization.Value)
			{
				lc_memsaver.Logger.LogInfo((object)"Applying render memory optimizations...");
				QualitySettings.pixelLightCount = PluginConfig.PixelLightCount.Value;
				QualitySettings.maximumLODLevel = PluginConfig.MaxLODLevel.Value;
				QualitySettings.shadowCascades = Math.Min(QualitySettings.shadowCascades, 2);
				if (PluginConfig.EnableMipmapStreaming.Value)
				{
					QualitySettings.streamingMipmapsActive = true;
					QualitySettings.streamingMipmapsMemoryBudget = 256f;
					QualitySettings.streamingMipmapsMaxLevelReduction = 2;
				}
				lc_memsaver.Logger.LogInfo((object)"Render optimizations applied.");
			}
		}
	}
	public static class MeshOptimizer
	{
		private static int _totalMeshesOptimized;

		private static long _estimatedBytesSaved;

		public static void OptimizeAllLoadedMeshes()
		{
			if (!PluginConfig.EnableMeshOptimization.Value)
			{
				return;
			}
			_totalMeshesOptimized = 0;
			_estimatedBytesSaved = 0L;
			Mesh[] array = Resources.FindObjectsOfTypeAll<Mesh>();
			lc_memsaver.Logger.LogInfo((object)$"Scanning {array.Length} loaded meshes for optimization...");
			Mesh[] array2 = array;
			foreach (Mesh val in array2)
			{
				try
				{
					OptimizeMesh(val);
				}
				catch (Exception ex)
				{
					lc_memsaver.Logger.LogDebug((object)("Could not optimize mesh '" + ((Object)val).name + "': " + ex.Message));
				}
			}
			lc_memsaver.Logger.LogInfo((object)($"Mesh optimization complete: {_totalMeshesOptimized} meshes marked non-readable, " + $"~{_estimatedBytesSaved / 1048576}MB estimated savings."));
		}

		private static void OptimizeMesh(Mesh mesh)
		{
			if (!((Object)(object)mesh == (Object)null) && mesh.isReadable && !IsMeshUsedByCollider(mesh) && mesh.vertexCount >= 32)
			{
				long num = EstimateMeshMemory(mesh);
				mesh.UploadMeshData(true);
				_estimatedBytesSaved += num;
				_totalMeshesOptimized++;
				lc_memsaver.Logger.LogDebug((object)$"Marked mesh '{((Object)mesh).name}' as non-readable ({mesh.vertexCount} verts, ~{num / 1024}KB freed)");
			}
		}

		private static bool IsMeshUsedByCollider(Mesh mesh)
		{
			MeshCollider[] array = Object.FindObjectsOfType<MeshCollider>();
			MeshCollider[] array2 = array;
			foreach (MeshCollider val in array2)
			{
				if ((Object)(object)val.sharedMesh == (Object)(object)mesh)
				{
					return true;
				}
			}
			return false;
		}

		private static long EstimateMeshMemory(Mesh mesh)
		{
			long num = 0L;
			num += (long)mesh.vertexCount * 12L;
			if (mesh.normals != null && mesh.normals.Length != 0)
			{
				num += (long)mesh.normals.Length * 12L;
			}
			if (mesh.uv != null && mesh.uv.Length != 0)
			{
				num += (long)mesh.uv.Length * 8L;
			}
			if (mesh.tangents != null && mesh.tangents.Length != 0)
			{
				num += (long)mesh.tangents.Length * 16L;
			}
			if (mesh.colors != null && mesh.colors.Length != 0)
			{
				num += (long)mesh.colors.Length * 16L;
			}
			for (int i = 0; i < mesh.subMeshCount; i++)
			{
				num += (long)mesh.GetTriangles(i).Length * 4L;
			}
			return num;
		}
	}
	public static class TextureOptimizer
	{
		private static int _totalTexturesOptimized;

		private static long _estimatedBytesSaved;

		public static void OptimizeAllLoadedTextures()
		{
			if (!PluginConfig.EnableTextureOptimization.Value)
			{
				return;
			}
			int value = PluginConfig.MaxTextureResolution.Value;
			_totalTexturesOptimized = 0;
			_estimatedBytesSaved = 0L;
			Texture2D[] array = Resources.FindObjectsOfTypeAll<Texture2D>();
			lc_memsaver.Logger.LogInfo((object)$"Scanning {array.Length} loaded textures for optimization...");
			Texture2D[] array2 = array;
			foreach (Texture2D val in array2)
			{
				try
				{
					OptimizeTexture(val, value);
				}
				catch (Exception ex)
				{
					lc_memsaver.Logger.LogDebug((object)("Could not optimize texture '" + ((Object)val).name + "': " + ex.Message));
				}
			}
			lc_memsaver.Logger.LogInfo((object)($"Texture optimization complete: {_totalTexturesOptimized} textures optimized, " + $"~{_estimatedBytesSaved / 1048576}MB estimated savings."));
		}

		private static void OptimizeTexture(Texture2D texture, int maxResolution)
		{
			if (!((Object)(object)texture == (Object)null) && (((Texture)texture).width > 64 || ((Texture)texture).height > 64) && (((Texture)texture).isReadable || CanForceCompress(texture)))
			{
				bool flag = false;
				long num = EstimateTextureMemory(texture);
				if (!PluginConfig.EnableMipmapStreaming.Value || !texture.streamingMipmaps)
				{
				}
				if (((Texture)texture).isReadable && (((Texture)texture).width > maxResolution || ((Texture)texture).height > maxResolution))
				{
					DownscaleTexture(texture, maxResolution);
					flag = true;
				}
				if (PluginConfig.ForceCompressTextures.Value && ((Texture)texture).isReadable && IsUncompressed(texture))
				{
					texture.Compress(true);
					flag = true;
				}
				if (flag)
				{
					long num2 = EstimateTextureMemory(texture);
					_estimatedBytesSaved += Math.Max(0L, num - num2);
					_totalTexturesOptimized++;
				}
			}
		}

		private static void DownscaleTexture(Texture2D texture, int maxResolution)
		{
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			int width = ((Texture)texture).width;
			int height = ((Texture)texture).height;
			float num = Math.Min((float)maxResolution / (float)width, (float)maxResolution / (float)height);
			if (num >= 1f)
			{
				return;
			}
			int num2 = Math.Max(1, (int)((float)width * num));
			int num3 = Math.Max(1, (int)((float)height * num));
			RenderTexture temporary = RenderTexture.GetTemporary(num2, num3, 0, (RenderTextureFormat)0);
			RenderTexture active = RenderTexture.active;
			Graphics.Blit((Texture)(object)texture, temporary);
			RenderTexture.active = temporary;
			try
			{
				texture.Reinitialize(num2, num3);
				texture.ReadPixels(new Rect(0f, 0f, (float)num2, (float)num3), 0, 0);
				texture.Apply(true, false);
				lc_memsaver.Logger.LogDebug((object)$"Downscaled texture '{((Object)texture).name}' from {width}x{height} to {num2}x{num3}");
			}
			finally
			{
				RenderTexture.active = active;
				RenderTexture.ReleaseTemporary(temporary);
			}
		}

		private static bool IsUncompressed(Texture2D texture)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Invalid comparison between Unknown and I4
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Invalid comparison between Unknown and I4
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Invalid comparison between Unknown and I4
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Invalid comparison between Unknown and I4
			return (int)texture.format == 4 || (int)texture.format == 5 || (int)texture.format == 3 || (int)texture.format == 14;
		}

		private static bool CanForceCompress(Texture2D texture)
		{
			return PluginConfig.ForceCompressTextures.Value && IsUncompressed(texture);
		}

		private static long EstimateTextureMemory(Texture2D texture)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Invalid comparison between Unknown and I4
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Invalid comparison between Unknown and I4
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Expected I4, but got Unknown
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Invalid comparison between Unknown and I4
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Expected I4, but got Unknown
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Invalid comparison between Unknown and I4
			TextureFormat format = texture.format;
			if (1 == 0)
			{
			}
			int num;
			if ((int)format <= 14)
			{
				switch (format - 3)
				{
				case 1:
					goto IL_005b;
				case 2:
					goto IL_0060;
				case 0:
					goto IL_0065;
				}
				switch (format - 10)
				{
				case 4:
					break;
				case 0:
					goto IL_006f;
				case 2:
					goto IL_0073;
				default:
					goto IL_0083;
				}
				num = 32;
			}
			else if ((int)format != 25)
			{
				if ((int)format != 47)
				{
					if ((int)format != 48)
					{
						goto IL_0083;
					}
					num = 8;
				}
				else
				{
					num = 8;
				}
			}
			else
			{
				num = 8;
			}
			goto IL_0088;
			IL_005b:
			num = 32;
			goto IL_0088;
			IL_0065:
			num = 24;
			goto IL_0088;
			IL_0083:
			num = 16;
			goto IL_0088;
			IL_0088:
			if (1 == 0)
			{
			}
			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_0073:
			num = 8;
			goto IL_0088;
			IL_006f:
			num = 4;
			goto IL_0088;
			IL_0060:
			num = 32;
			goto IL_0088;
		}
	}
}
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<int> MaxTextureResolution { get; private set; }

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

		public static ConfigEntry<bool> EnableMipmapStreaming { 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> EnableMeshOptimization { 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<bool> EnableRenderOptimization { get; private set; }

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

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

		public static void Initialize(ConfigFile config)
		{
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Expected O, but got Unknown
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Expected O, but got Unknown
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0186: Expected O, but got Unknown
			//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01eb: Expected O, but got Unknown
			//IL_020e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0218: 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 automatic texture resolution reduction and compression.");
			MaxTextureResolution = config.Bind<int>("Texture Optimization", "MaxTextureResolution", 2048, new ConfigDescription("Maximum texture resolution. Textures larger than this will be downscaled. Lower = less RAM, less quality.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(256, 4096), Array.Empty<object>()));
			ForceCompressTextures = config.Bind<bool>("Texture Optimization", "ForceCompressTextures", true, "Force-compress uncompressed textures to DXT format to save VRAM and RAM.");
			EnableMipmapStreaming = config.Bind<bool>("Texture Optimization", "EnableMipmapStreaming", true, "Enable Unity mipmap streaming to load only the mip levels needed.");
			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", 10f, new ConfigDescription("Audio clips longer than this (in seconds) will be marked for streaming to save RAM.", (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.");
			EnableMeshOptimization = config.Bind<bool>("Mesh Optimization", "EnableMeshOptimization", true, "Mark meshes as non-readable after GPU upload to free CPU-side memory.");
			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.");
			EnableRenderOptimization = config.Bind<bool>("Render Optimization", "EnableRenderOptimization", false, "Apply render settings to reduce memory usage (may affect visual quality).");
			PixelLightCount = config.Bind<int>("Render Optimization", "PixelLightCount", 2, new ConfigDescription("Max number of per-pixel lights. Fewer lights = less GPU/CPU memory.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 8), Array.Empty<object>()));
			MaxLODLevel = config.Bind<int>("Render Optimization", "MaxLODLevel", 0, new ConfigDescription("Force minimum LOD level (0 = highest quality, 3 = lowest). Higher values use simpler meshes.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 3), Array.Empty<object>()));
		}
	}
}