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>()));
}
}
}