Decompiled source of ModpackDoctor v1.2.0

ModpackDoctor.dll

Decompiled 8 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[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 ModpackDoctor
{
	[BepInPlugin("local.modpackdoctor", "ModpackDoctor", "1.2.0")]
	public class ModpackDoctorPlugin : BaseUnityPlugin
	{
		private class Rule
		{
			public string File;

			public string Key;

			public string Why;

			public string Suggested;

			public string Section;

			public Func<string, bool> IsBad;
		}

		public const string PluginGuid = "local.modpackdoctor";

		public const string PluginName = "ModpackDoctor";

		public const string PluginVersion = "1.2.0";

		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> FilterRepeatedLines;

		internal static ConfigEntry<int> RepeatsBeforeSuppressing;

		internal static ConfigEntry<float> RepeatWindowSeconds;

		internal static ConfigEntry<string> AlwaysHidePatterns;

		private ConfigEntry<bool> _runCheckUp;

		private ConfigEntry<bool> _reportHealthySettings;

		private Harmony _harmony;

		private static readonly Rule[] Rules = new Rule[7]
		{
			new Rule
			{
				File = "VirusTLNR.LethalIntelligence.cfg",
				Key = "Debug Mode",
				IsBad = IsTrue,
				Why = "Rebuilds a 44-field debug string 50 times a second for every masked enemy, on every machine.",
				Suggested = "false"
			},
			new Rule
			{
				File = "VirusTLNR.LethalIntelligence.cfg",
				Key = "Status Report Delay",
				IsBad = (string v) => NumberIs(v, 0f),
				Why = "0 does NOT mean off - the check is 'count < 0', which is never true, so the throttle never engages and it runs every tick.",
				Suggested = "250"
			},
			new Rule
			{
				File = "ShaosilGaming.GeneralImprovements.cfg",
				Key = "ShipInternalCamFPS",
				IsBad = (string v) => NumberIs(v, 0f),
				Why = "0 means unrestricted, so this camera re-renders the scene every single frame while you are in the ship.",
				Suggested = "5"
			},
			new Rule
			{
				File = "ShaosilGaming.GeneralImprovements.cfg",
				Key = "ShipExternalCamFPS",
				IsBad = (string v) => NumberIs(v, 0f),
				Why = "0 means unrestricted - a second always-on camera render.",
				Suggested = "5"
			},
			new Rule
			{
				File = "Sygnano.OpenBodyCams-V81.cfg",
				Key = "Framerate",
				IsBad = (string v) => NumberIs(v, 0f),
				Why = "0 means the body cam renders the whole scene again every frame.",
				Suggested = "30"
			},
			new Rule
			{
				File = "Mirage.General.cfg",
				Key = "Masked enemy spawn chance",
				IsBad = (string v) => NumberAbove(v, 10f),
				Why = "The mod's own notes say 25 is a near-guaranteed spawn every round. Masked are the most expensive enemy in a modded pack, so this multiplies every other cost.",
				Suggested = "below 10, default is 0.5"
			},
			new Rule
			{
				File = "BepInEx.cfg",
				Key = "LogLevels",
				Section = "Logging.Disk",
				IsBad = (string v) => v.IndexOf("All", StringComparison.OrdinalIgnoreCase) >= 0 || v.IndexOf("Debug", StringComparison.OrdinalIgnoreCase) >= 0,
				Why = "Writes Debug-level messages to disk. Not usually the main cost, but it makes the log far larger and harder to read.",
				Suggested = "Fatal, Error, Warning, Message, Info"
			}
		};

		private void Awake()
		{
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Expected O, but got Unknown
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Expected O, but got Unknown
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0114: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			_runCheckUp = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - Check-up", "RunCheckUp", true, "true = look through your other mods' settings at startup and warn about anything known to cost frame rate.");
			_reportHealthySettings = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - Check-up", "ReportHealthySettings", false, "true = also say when a setting was checked and looked fine. Useful once, noisy afterwards.");
			FilterRepeatedLines = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Log filter", "FilterRepeatedLines", true, "true = when a mod writes the same line over and over, stop writing it and note how many were skipped. Cuts the log down enormously and saves the disk writes, which happen on the main thread and cost frame time.");
			RepeatsBeforeSuppressing = ((BaseUnityPlugin)this).Config.Bind<int>("2 - Log filter", "RepeatsBeforeSuppressing", 25, new ConfigDescription("How many identical lines to allow before the rest are collapsed. You still see the message, just not thousands of copies.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(3, 1000), Array.Empty<object>()));
			RepeatWindowSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("2 - Log filter", "RepeatWindowSeconds", 30f, new ConfigDescription("After this long without seeing a message, its count resets and it is allowed through again.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 600f), Array.Empty<object>()));
			AlwaysHidePatterns = ((BaseUnityPlugin)this).Config.Bind<string>("2 - Log filter", "AlwaysHidePatterns", "", "Optional. Comma separated pieces of text; any log line containing one is hidden immediately, without waiting for repeats. Leave empty unless something is drowning your log. Example: Could not access dying player,GrabAnimation not found");
			_harmony = new Harmony("local.modpackdoctor");
			if (FilterRepeatedLines.Value || AlwaysHidePatterns.Value.Trim().Length > 0)
			{
				TryPatchLogListeners();
			}
			if (_runCheckUp.Value)
			{
				RunCheckUp();
			}
			Log.LogInfo((object)"ModpackDoctor 1.2.0 loaded.");
		}

		private void TryPatchLogListeners()
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Expected O, but got Unknown
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Expected O, but got Unknown
			try
			{
				Type type = AccessTools.TypeByName("BepInEx.Logging.LogListenerCollection");
				MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, "SendLogEvent", (Type[])null, (Type[])null));
				if (methodInfo != null)
				{
					_harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(AccessTools.Method(typeof(LogFilter), "Prefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					Log.LogInfo((object)"[ModpackDoctor] Log filter active - one check per line.");
					return;
				}
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("[ModpackDoctor] Could not filter centrally (" + ex.Message + "); falling back."));
			}
			int num = 0;
			string[] array = new string[2] { "BepInEx.Logging.DiskLogListener", "BepInEx.Logging.ConsoleLogListener" };
			foreach (string text in array)
			{
				try
				{
					Type type2 = AccessTools.TypeByName(text);
					if (!(type2 == null))
					{
						MethodInfo methodInfo2 = AccessTools.Method(type2, "LogEvent", (Type[])null, (Type[])null);
						if (!(methodInfo2 == null))
						{
							_harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(AccessTools.Method(typeof(LogFilter), "Prefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
							num++;
						}
					}
				}
				catch (Exception ex2)
				{
					Log.LogWarning((object)("[ModpackDoctor] Could not filter " + text + ": " + ex2.Message));
				}
			}
			Log.LogWarning((object)string.Format("[{0}] Log filter active on {1} writer(s) - fallback, repeats count twice.", "ModpackDoctor", num));
		}

		private void RunCheckUp()
		{
			string configPath = Paths.ConfigPath;
			List<string> list = new List<string>();
			List<string> list2 = new List<string>();
			Rule[] rules = Rules;
			foreach (Rule rule in rules)
			{
				string path = Path.Combine(configPath, rule.File);
				if (!File.Exists(path))
				{
					continue;
				}
				string text = ReadSetting(path, rule.Key, rule.Section);
				if (text != null)
				{
					if (rule.IsBad(text))
					{
						list.Add("  " + rule.File + " -> " + rule.Key + " = " + text + "\n      " + rule.Why + "\n      suggested: " + rule.Suggested);
					}
					else
					{
						list2.Add("  " + rule.File + " -> " + rule.Key + " = " + text + "  (fine)");
					}
				}
			}
			Log.LogInfo((object)"================ modpack check-up ================");
			if (list.Count == 0)
			{
				Log.LogInfo((object)"  Nothing known to be costly was found.");
			}
			else
			{
				Log.LogWarning((object)$"  {list.Count} setting(s) worth changing:");
				foreach (string item in list)
				{
					Log.LogWarning((object)item);
				}
				Log.LogWarning((object)"  Note: most mods do NOT share settings over the network, so each");
				Log.LogWarning((object)"  player usually has to change their own copy for it to help them.");
			}
			if (_reportHealthySettings.Value)
			{
				foreach (string item2 in list2)
				{
					Log.LogInfo((object)item2);
				}
			}
			Log.LogInfo((object)"=================================================");
		}

		private static string ReadSetting(string path, string key, string section)
		{
			try
			{
				string a = null;
				string[] array = File.ReadAllLines(path);
				for (int i = 0; i < array.Length; i++)
				{
					string text = array[i].Trim();
					if (text.Length == 0 || text[0] == '#')
					{
						continue;
					}
					if (text[0] == '[')
					{
						int num = text.IndexOf(']');
						a = ((num > 1) ? text.Substring(1, num - 1).Trim() : null);
					}
					else if (section == null || string.Equals(a, section, StringComparison.OrdinalIgnoreCase))
					{
						int num2 = text.IndexOf('=');
						if (num2 > 0 && string.Equals(text.Substring(0, num2).Trim(), key, StringComparison.OrdinalIgnoreCase))
						{
							return text.Substring(num2 + 1).Trim();
						}
					}
				}
			}
			catch
			{
			}
			return null;
		}

		private static bool IsTrue(string v)
		{
			return string.Equals(v, "true", StringComparison.OrdinalIgnoreCase);
		}

		private static bool NumberIs(string v, float target)
		{
			if (float.TryParse(v, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				return Math.Abs(result - target) < 0.0001f;
			}
			return false;
		}

		private static bool NumberAbove(string v, float limit)
		{
			if (float.TryParse(v, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				return result > limit;
			}
			return false;
		}
	}
	internal static class LogFilter
	{
		private class Seen
		{
			public int Count;

			public float LastTime;

			public bool Announced;
		}

		private static readonly Dictionary<string, Seen> Recent = new Dictionary<string, Seen>();

		private static readonly object Gate = new object();

		[ThreadStatic]
		private static bool _insideFilter;

		private static string[] _hidePatterns = new string[0];

		private static string _lastPatternText = null;

		private static float _nextTidy;

		internal static int TotalSuppressed;

		private static readonly Stopwatch Clock = Stopwatch.StartNew();

		internal static bool Prefix(object eventArgs)
		{
			if (_insideFilter)
			{
				return true;
			}
			_insideFilter = true;
			try
			{
				if (eventArgs == null)
				{
					return true;
				}
				string text = null;
				LogEventArgs e = (LogEventArgs)((eventArgs is LogEventArgs) ? eventArgs : null);
				text = ((e == null) ? eventArgs.ToString() : ((e.Data == null) ? null : e.Data.ToString()));
				if (string.IsNullOrEmpty(text))
				{
					return true;
				}
				RefreshPatterns();
				string[] hidePatterns = _hidePatterns;
				foreach (string text2 in hidePatterns)
				{
					if (text2.Length > 0 && text.IndexOf(text2, StringComparison.OrdinalIgnoreCase) >= 0)
					{
						return false;
					}
				}
				if (!ModpackDoctorPlugin.FilterRepeatedLines.Value)
				{
					return true;
				}
				float num = (float)Clock.Elapsed.TotalSeconds;
				float value = ModpackDoctorPlugin.RepeatWindowSeconds.Value;
				bool flag = false;
				bool flag2;
				lock (Gate)
				{
					if (num > _nextTidy)
					{
						_nextTidy = num + 60f;
						List<string> list = new List<string>();
						foreach (KeyValuePair<string, Seen> item in Recent)
						{
							if (num - item.Value.LastTime > value * 2f)
							{
								list.Add(item.Key);
							}
						}
						foreach (string item2 in list)
						{
							Recent.Remove(item2);
						}
					}
					if (!Recent.TryGetValue(text, out var value2))
					{
						Recent[text] = new Seen
						{
							Count = 1,
							LastTime = num
						};
						flag2 = true;
					}
					else if (num - value2.LastTime > value)
					{
						value2.Count = 1;
						value2.LastTime = num;
						value2.Announced = false;
						flag2 = true;
					}
					else
					{
						value2.Count++;
						value2.LastTime = num;
						int value3 = ModpackDoctorPlugin.RepeatsBeforeSuppressing.Value;
						flag2 = value2.Count <= value3;
						if (!flag2)
						{
							TotalSuppressed++;
						}
						if (!flag2 && !value2.Announced)
						{
							value2.Announced = true;
							flag = true;
						}
					}
				}
				if (flag)
				{
					ModpackDoctorPlugin.Log.LogWarning((object)("[ModpackDoctor] Silencing a line repeated more than " + $"{ModpackDoctorPlugin.RepeatsBeforeSuppressing.Value} times: " + text.Substring(0, Math.Min(120, text.Length))));
				}
				return flag2;
			}
			catch
			{
				return true;
			}
			finally
			{
				_insideFilter = false;
			}
		}

		private static void RefreshPatterns()
		{
			string value = ModpackDoctorPlugin.AlwaysHidePatterns.Value;
			if (value == _lastPatternText)
			{
				return;
			}
			_lastPatternText = value;
			List<string> list = new List<string>();
			string[] array = value.Split(',');
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length > 0)
				{
					list.Add(text);
				}
			}
			_hidePatterns = list.ToArray();
		}
	}
}