Decompiled source of ResourceUnloadOptimizer v1.0.3

ResourceUnloadOptimizer.dll

Decompiled 3 months ago
using System;
using System.Collections;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
using MonoMod.RuntimeDetour;
using ResourceUnloadOptimizer;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("ResourceUnloadOptimizer")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Azumatt")]
[assembly: AssemblyProduct("ResourceUnloadOptimizer")]
[assembly: AssemblyCopyright("Copyright ©  2022")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("E0E2F92E-557C-4A05-9D89-AA92A0BD75C4")]
[assembly: AssemblyFileVersion("1.0.3")]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.3.0")]
[module: UnverifiableCode]
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;
		}
	}
}
namespace ResourceUnloadOptimizer
{
	[BepInPlugin("Azumatt.ResourceUnloadOptimizer", "ResourceUnloadOptimizer", "1.0.3")]
	public class ResourceUnloadOptimizerPlugin : BaseUnityPlugin
	{
		public enum Toggle
		{
			On = 1,
			Off = 0
		}

		private static class Hooks
		{
			[HarmonyPrefix]
			[HarmonyPatch(typeof(GC), "Collect", new Type[] { })]
			public static bool GCCollectHook()
			{
				_garbageCollect = 3;
				return false;
			}

			public static AsyncOperation UnloadUnusedAssetsHook()
			{
				if (DisableUnload.Value != Toggle.On)
				{
					return RunUnloadAssets();
				}
				return null;
			}
		}

		private class ConfigurationManagerAttributes
		{
			[UsedImplicitly]
			public int? Order;

			[UsedImplicitly]
			public bool? Browsable;

			[UsedImplicitly]
			public string? Category;

			[UsedImplicitly]
			public Action<ConfigEntryBase>? CustomDrawer;
		}

		internal const string ModName = "ResourceUnloadOptimizer";

		internal const string ModVersion = "1.0.3";

		internal const string Author = "Azumatt";

		private const string ModGUID = "Azumatt.ResourceUnloadOptimizer";

		private static string ConfigFileName = "Azumatt.ResourceUnloadOptimizer.cfg";

		private static string ConfigFileFullPath;

		private readonly Harmony _harmony = new Harmony("Azumatt.ResourceUnloadOptimizer");

		public static readonly ManualLogSource ResourceUnloadOptimizerLogger;

		private static AsyncOperation _currentOperation;

		private static Func<AsyncOperation> _originalUnload;

		private static int _garbageCollect;

		private float _waitTime;

		private static ConfigEntry<Toggle> DisableUnload;

		private static ConfigEntry<Toggle> OptimizeMemoryUsage;

		private static ConfigEntry<int> PercentMemoryThreshold;

		public void Awake()
		{
			DisableUnload = config("1 - General", "DisableUnload", Toggle.Off, "Disable the unloading of all resources. Requires large amounts of RAM or will likely crash your game. NOT RECOMMENDED FOR NORMAL USE.");
			OptimizeMemoryUsage = config("1 - General", "OptimizeMemoryUsage", Toggle.On, "Use more memory (if available) in order to load the game faster and reduce random stuttering.");
			PercentMemoryThreshold = config("1 - General", "PercentMemoryThreshold", 75, "Minimum amount of memory to be used before resource unloading will run.");
			InstallHooks();
			((MonoBehaviour)this).StartCoroutine(CleanupCo());
			Assembly executingAssembly = Assembly.GetExecutingAssembly();
			_harmony.PatchAll(executingAssembly);
			SetupWatcher();
		}

		private static void InstallHooks()
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			MethodInfo methodInfo = AccessTools.Method(typeof(Resources), "UnloadUnusedAssets", (Type[])null, (Type[])null);
			MethodInfo methodInfo2 = AccessTools.Method(typeof(Hooks), "UnloadUnusedAssetsHook", (Type[])null, (Type[])null);
			NativeDetour val = new NativeDetour((MethodBase)methodInfo, (MethodBase)methodInfo2);
			val.Apply();
			_originalUnload = val.GenerateTrampoline<Func<AsyncOperation>>();
		}

		private IEnumerator CleanupCo()
		{
			while (true)
			{
				if (Time.realtimeSinceStartup < _waitTime)
				{
					yield return null;
					continue;
				}
				_waitTime = Time.realtimeSinceStartup + 1f;
				if (_garbageCollect > 0 && --_garbageCollect == 0)
				{
					RunGarbageCollect();
				}
			}
		}

		private static AsyncOperation RunUnloadAssets()
		{
			if (_currentOperation == null || (_currentOperation.isDone && !PlentyOfMemory()))
			{
				ResourceUnloadOptimizerLogger.LogDebug((object)"Starting unused asset cleanup");
				_currentOperation = _originalUnload();
			}
			return _currentOperation;
		}

		private static void RunGarbageCollect()
		{
			if (!PlentyOfMemory())
			{
				ResourceUnloadOptimizerLogger.LogDebug((object)"Starting full garbage collection");
				GC.Collect(GC.MaxGeneration);
			}
		}

		private static bool PlentyOfMemory()
		{
			if (OptimizeMemoryUsage.Value == Toggle.Off)
			{
				return false;
			}
			MemoryInfo.MEMORYSTATUSEX currentStatus = MemoryInfo.GetCurrentStatus();
			if (currentStatus == null)
			{
				return false;
			}
			float num = (float)currentStatus.ullAvailPageFile / (float)currentStatus.ullTotalPageFile;
			if (currentStatus.dwMemoryLoad >= PercentMemoryThreshold.Value || !(num > 0.3f) || currentStatus.ullAvailPageFile <= 2147483648u)
			{
				return false;
			}
			ResourceUnloadOptimizerLogger.LogDebug((object)$"Skipping cleanup because of low memory load ({currentStatus.dwMemoryLoad}% RAM, {100 - (int)(num * 100f)}% Page file, {currentStatus.ullAvailPageFile / 1024 / 1024}MB available in PF)");
			return true;
		}

		private void OnDestroy()
		{
			((BaseUnityPlugin)this).Config.Save();
		}

		private void SetupWatcher()
		{
			FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(Paths.ConfigPath, ConfigFileName);
			fileSystemWatcher.Changed += ReadConfigValues;
			fileSystemWatcher.Created += ReadConfigValues;
			fileSystemWatcher.Renamed += ReadConfigValues;
			fileSystemWatcher.IncludeSubdirectories = true;
			fileSystemWatcher.SynchronizingObject = ThreadingHelper.SynchronizingObject;
			fileSystemWatcher.EnableRaisingEvents = true;
		}

		private void ReadConfigValues(object sender, FileSystemEventArgs e)
		{
			if (!File.Exists(ConfigFileFullPath))
			{
				return;
			}
			try
			{
				ResourceUnloadOptimizerLogger.LogDebug((object)"ReadConfigValues called");
				((BaseUnityPlugin)this).Config.Reload();
			}
			catch
			{
				ResourceUnloadOptimizerLogger.LogError((object)("There was an issue loading your " + ConfigFileName));
				ResourceUnloadOptimizerLogger.LogError((object)"Please check your config entries for spelling and format!");
			}
		}

		private ConfigEntry<T> config<T>(string group, string name, T value, ConfigDescription description)
		{
			return ((BaseUnityPlugin)this).Config.Bind<T>(group, name, value, description);
		}

		private ConfigEntry<T> config<T>(string group, string name, T value, string description)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			return config(group, name, value, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>()));
		}

		static ResourceUnloadOptimizerPlugin()
		{
			string configPath = Paths.ConfigPath;
			char directorySeparatorChar = Path.DirectorySeparatorChar;
			ConfigFileFullPath = configPath + directorySeparatorChar + ConfigFileName;
			ResourceUnloadOptimizerLogger = Logger.CreateLogSource("ResourceUnloadOptimizer");
			DisableUnload = null;
			OptimizeMemoryUsage = null;
			PercentMemoryThreshold = null;
		}
	}
}
namespace BepInEx
{
	internal static class MemoryInfo
	{
		[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
		public class MEMORYSTATUSEX
		{
			public uint dwLength;

			public uint dwMemoryLoad;

			public ulong ullTotalPhys;

			public ulong ullAvailPhys;

			public ulong ullTotalPageFile;

			public ulong ullAvailPageFile;

			public ulong ullTotalVirtual;

			public ulong ullAvailVirtual;

			public ulong ullAvailExtendedVirtual;

			public MEMORYSTATUSEX()
			{
				dwLength = (uint)Marshal.SizeOf(typeof(MEMORYSTATUSEX));
			}
		}

		public static MEMORYSTATUSEX GetCurrentStatus()
		{
			try
			{
				MEMORYSTATUSEX mEMORYSTATUSEX = new MEMORYSTATUSEX();
				if (GlobalMemoryStatusEx(mEMORYSTATUSEX))
				{
					return mEMORYSTATUSEX;
				}
				return null;
			}
			catch (Exception ex)
			{
				ResourceUnloadOptimizerPlugin.ResourceUnloadOptimizerLogger.LogError((object)ex);
				return null;
			}
		}

		[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
		[return: MarshalAs(UnmanagedType.Bool)]
		private static extern bool GlobalMemoryStatusEx([In][Out] MEMORYSTATUSEX lpBuffer);
	}
}