Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of lc memsaver v1.0.0
yutho.lc-memsaver.dll
Decompiled 7 months agousing 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>())); } } }