Decompiled source of ModBlame v1.0.0

ModBlame.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Permissions;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Mono.Cecil;
using Mono.Cecil.Cil;
using MonoMod.Cil;
using MonoMod.RuntimeDetour;
using MonoMod.Utils;
using RiskOfOptions;
using RiskOfOptions.Options;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("Chinchi")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Find out which mods are doing cursed stuff.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+065423eb8b2e82ee71315a7836a5f31391b01331")]
[assembly: AssemblyProduct("ModBlame")]
[assembly: AssemblyTitle("ModBlame")]
[assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/schinchi/ModBlame")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
[CompilerGenerated]
internal delegate void <>f__AnonymousDelegate0<T1, T2, T3, T4>(T1 arg1, T2 arg2, T3 arg3, params T4[] arg4);
[CompilerGenerated]
internal delegate void <>f__AnonymousDelegate1<T1, T2, T3>(T1 arg1, T2 arg2, params T3[] arg3);
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 ModBlame
{
	internal class HookNoOrig
	{
		private delegate void PatchInfoAddPrefixesDelegate(PatchInfo patchInfo, string owner, params HarmonyMethod[] methods);

		private delegate void HookConstructorDelegate(Hook hook, MethodBase from, MethodInfo to, object target, ref HookConfig config);

		[CompilerGenerated]
		private static class <>O
		{
			public static EventHandler<SettingChangedEventArgs> <0>__ParseSettings;

			public static <>f__AnonymousDelegate0<PatchInfoAddPrefixesDelegate, PatchInfo, string, HarmonyMethod> <1>__InspectHarmonyPrefixForReturnFalse;

			public static <>A{00008000}<HookConstructorDelegate, Hook, MethodBase, MethodInfo, object, HookConfig> <2>__InspectHookForNoOrigInvoke;

			public static Manipulator <3>__ScanForReturnFalse;

			public static Manipulator <4>__ScanForNoOrigInvoke;
		}

		private static List<Regex> whitelist = new List<Regex>();

		private static string origName = null;

		internal static void Init()
		{
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			PluginConfig.file.SettingChanged += ParseSettings;
			ParseSettings(null, null);
			new Hook((MethodBase)typeof(PatchInfo).GetMethod("AddPrefixes", (BindingFlags)(-1)), (Delegate)new <>f__AnonymousDelegate0<PatchInfoAddPrefixesDelegate, PatchInfo, string, HarmonyMethod>(InspectHarmonyPrefixForReturnFalse));
			new Hook((MethodBase)typeof(Hook).GetConstructor(new Type[4]
			{
				typeof(MethodBase),
				typeof(MethodInfo),
				typeof(object),
				typeof(HookConfig).MakeByRefType()
			}), (Delegate)new <>A{00008000}<HookConstructorDelegate, Hook, MethodBase, MethodInfo, object, HookConfig>(InspectHookForNoOrigInvoke));
		}

		private static void ParseSettings(object sender, SettingChangedEventArgs e)
		{
			whitelist = (from p in Utils.SplitText(PluginConfig.noOrigWhitelist.Value)
				select new Regex(p)).ToList();
		}

		private static void InspectHarmonyPrefixForReturnFalse(PatchInfoAddPrefixesDelegate orig, PatchInfo self, string owner, params HarmonyMethod[] methods)
		{
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Expected O, but got Unknown
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Expected O, but got Unknown
			orig(self, owner, methods);
			for (int i = 0; i < methods.Length; i++)
			{
				MethodInfo methodInfo = methods[i]?.method;
				if (!(methodInfo != null) || !(methodInfo.ReturnType == typeof(bool)))
				{
					continue;
				}
				string text = $"{methodInfo.DeclaringType}.{methodInfo.Name}";
				if (!PluginConfig.noOrigEnabled.Value || Utils.MatchPattern(text, whitelist))
				{
					continue;
				}
				ILHook val = null;
				try
				{
					object obj = <>O.<3>__ScanForReturnFalse;
					if (obj == null)
					{
						Manipulator val2 = ScanForReturnFalse;
						<>O.<3>__ScanForReturnFalse = val2;
						obj = (object)val2;
					}
					val = new ILHook((MethodBase)methodInfo, (Manipulator)obj, new ILHookConfig
					{
						ManualApply = true
					});
					val.Apply();
				}
				catch (Exception arg)
				{
					Log.Error($"Failed to investigate {text} for Harmony prefix\n{arg}");
				}
				finally
				{
					if (val != null)
					{
						val.Dispose();
					}
				}
			}
		}

		private static void ScanForReturnFalse(ILContext il)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			if (new ILCursor(il).TryGotoNext(new Func<Instruction, bool>[2]
			{
				(Instruction x) => ILPatternMatchingExt.MatchLdcI4(x, 0),
				(Instruction x) => ILPatternMatchingExt.MatchRet(x)
			}))
			{
				string text = ((MemberReference)il.Method).Name.Replace("::", ".");
				Log.Warning("HarmonyPrefix returns false from " + text);
			}
		}

		private static void InspectHookForNoOrigInvoke(HookConstructorDelegate orig, Hook self, MethodBase method, MethodInfo to, object target, ref HookConfig hookConfig)
		{
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: Expected O, but got Unknown
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Expected O, but got Unknown
			orig(self, method, to, target, ref hookConfig);
			string text = $"{to.DeclaringType}.{to.Name}";
			if (!PluginConfig.noOrigEnabled.Value || Utils.MatchPattern(text, whitelist))
			{
				return;
			}
			ILHook val = null;
			try
			{
				MethodInfo methodInfo = ((to.ReturnType == typeof(IEnumerator) || (to.ReturnType.IsGenericType && to.ReturnType.GetGenericTypeDefinition() == typeof(IEnumerator<>))) ? Extensions.GetStateMachineTarget(to) : to);
				origName = "NULL";
				ParameterInfo[] parameters = to.GetParameters();
				foreach (ParameterInfo parameterInfo in parameters)
				{
					if (typeof(Delegate).IsAssignableFrom(parameterInfo.ParameterType))
					{
						origName = parameterInfo.ParameterType.Name;
					}
				}
				object obj = <>O.<4>__ScanForNoOrigInvoke;
				if (obj == null)
				{
					Manipulator val2 = ScanForNoOrigInvoke;
					<>O.<4>__ScanForNoOrigInvoke = val2;
					obj = (object)val2;
				}
				val = new ILHook((MethodBase)methodInfo, (Manipulator)obj, new ILHookConfig
				{
					ManualApply = true
				});
				val.Apply();
			}
			catch (Exception arg)
			{
				Log.Error($"Failed to investigate {text} for not calling orig\n{arg}");
			}
			finally
			{
				if (val != null)
				{
					val.Dispose();
				}
			}
		}

		private static void ScanForNoOrigInvoke(ILContext il)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Expected O, but got Unknown
			ILCursor val = new ILCursor(il);
			MethodReference methodReference = null;
			while (val.TryGotoNext(new Func<Instruction, bool>[1]
			{
				(Instruction x) => ILPatternMatchingExt.MatchCallOrCallvirt(x, ref methodReference)
			}))
			{
				if (((MemberReference)methodReference).FullName.Contains(origName))
				{
					return;
				}
			}
			string text = ((MemberReference)il.Method).Name.Replace("::", ".");
			Log.Warning("Orig skipped by " + text);
		}
	}
	internal class ILCursorRemove
	{
		private static readonly HashSet<string> removeEvents = new HashSet<string>();

		private static readonly HashSet<string> removeRangeEvents = new HashSet<string>();

		private static List<Regex> whitelist = new List<Regex>();

		internal static void Init()
		{
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			PluginConfig.file.SettingChanged += ParseSettings;
			ParseSettings(null, null);
			new Hook((MethodBase)typeof(ILCursor).GetMethod("Remove"), (Delegate)new Func<Func<ILCursor, ILCursor>, ILCursor, ILCursor>(LogILCursorRemove));
			new Hook((MethodBase)typeof(ILCursor).GetMethod("RemoveRange"), (Delegate)new Func<Func<ILCursor, int, ILCursor>, ILCursor, int, ILCursor>(LogILCursorRemoveRange));
		}

		private static void ParseSettings(object sender, SettingChangedEventArgs e)
		{
			whitelist = (from p in Utils.SplitText(PluginConfig.cursorRemoveWhitelist.Value)
				select new Regex(p)).ToList();
		}

		private static ILCursor LogILCursorRemove(Func<ILCursor, ILCursor> orig, ILCursor self)
		{
			if (PluginConfig.cursorRemoveEnabled.Value)
			{
				string text = ExtractCaller();
				if (!removeEvents.Contains(text) && !Utils.MatchPattern(text, whitelist))
				{
					Log.Warning("ILCursor.Remove called by " + text);
					removeEvents.Add(text);
				}
			}
			return orig(self);
		}

		private static ILCursor LogILCursorRemoveRange(Func<ILCursor, int, ILCursor> orig, ILCursor self, int num)
		{
			if (PluginConfig.cursorRemoveEnabled.Value)
			{
				string text = ExtractCaller();
				if (!removeRangeEvents.Contains(text) && !Utils.MatchPattern(text, whitelist))
				{
					Log.Warning("ILCursor.RemoveRange called by " + text);
					removeRangeEvents.Add(text);
				}
			}
			return orig(self, num);
		}

		private static string ExtractCaller()
		{
			StackTrace stackTrace = new StackTrace();
			StackFrame[] frames = stackTrace.GetFrames();
			for (int i = 0; i < frames.Length; i++)
			{
				MethodBase method = frames[i].GetMethod();
				ParameterInfo[] parameters = method.GetParameters();
				for (int j = 0; j < parameters.Length; j++)
				{
					if (parameters[j].ParameterType == typeof(ILContext))
					{
						return $"{method.DeclaringType}.{method.Name}";
					}
				}
			}
			return stackTrace.ToString();
		}
	}
	internal static class Log
	{
		private static ManualLogSource _logSource;

		internal static void Init(ManualLogSource logSource)
		{
			_logSource = logSource;
		}

		internal static void Debug(object data)
		{
			_logSource.LogDebug(data);
		}

		internal static void Error(object data)
		{
			_logSource.LogError(data);
		}

		internal static void Fatal(object data)
		{
			_logSource.LogFatal(data);
		}

		internal static void Info(object data)
		{
			_logSource.LogInfo(data);
		}

		internal static void Message(object data)
		{
			_logSource.LogMessage(data);
		}

		internal static void Warning(object data)
		{
			_logSource.LogWarning(data);
		}
	}
	internal class LogSpam
	{
		private delegate void LogDelegate_string_params(string text, params object[] args);

		private delegate void LogDelegate_Object_string_params(Object context, string text, params object[] args);

		private static List<Regex> blacklist = new List<Regex>();

		internal static void Init()
		{
			PluginConfig.file.SettingChanged += ParseSettings;
			ParseSettings(null, null);
			ApplyHooks(new Type[1] { typeof(object) }, new Action<Action<object>, object>(CatchLog_object));
			ApplyHooks(new Type[2]
			{
				typeof(object),
				typeof(Object)
			}, new Action<Action<object, Object>, object, Object>(CatchLog_object_Object));
			ApplyHooks(new Type[2]
			{
				typeof(string),
				typeof(object[])
			}, new <>f__AnonymousDelegate1<LogDelegate_string_params, string, object>(CatchLog_string_params), isFormat: true);
			ApplyHooks(new Type[3]
			{
				typeof(Object),
				typeof(string),
				typeof(object[])
			}, new <>f__AnonymousDelegate0<LogDelegate_Object_string_params, Object, string, object>(CatchLog_Object_string_params), isFormat: true);
			static void ApplyHooks(Type[] arguments, Delegate delegateMethod, bool isFormat = false)
			{
				//IL_002c: Unknown result type (might be due to invalid IL or missing references)
				//IL_004e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0070: Unknown result type (might be due to invalid IL or missing references)
				string text = (isFormat ? "Format" : "");
				new Hook((MethodBase)typeof(Debug).GetMethod("Log" + text, arguments), delegateMethod);
				new Hook((MethodBase)typeof(Debug).GetMethod("LogWarning" + text, arguments), delegateMethod);
				new Hook((MethodBase)typeof(Debug).GetMethod("LogError" + text, arguments), delegateMethod);
			}
		}

		private static void ParseSettings(object sender, SettingChangedEventArgs e)
		{
			blacklist = (from p in Utils.SplitText(PluginConfig.logSpamText.Value)
				select new Regex(p)).ToList();
		}

		private static void CatchLog_object(Action<object> orig, object message)
		{
			CheckForSpam(message);
			orig(message);
		}

		private static void CatchLog_object_Object(Action<object, Object> orig, object message, Object context)
		{
			CheckForSpam(message);
			orig(message, context);
		}

		private static void CatchLog_string_params(LogDelegate_string_params orig, string text, params object[] args)
		{
			CheckForSpam(string.Format(text, args));
			orig(text, args);
		}

		private static void CatchLog_Object_string_params(LogDelegate_Object_string_params orig, Object context, string text, params object[] args)
		{
			CheckForSpam(string.Format(text, args));
			orig(context, text, args);
		}

		private static void CheckForSpam(object message)
		{
			if (PluginConfig.logSpamEnabled.Value && Utils.MatchPattern(message.ToString(), blacklist))
			{
				Log.Warning($"Blacklisted text \"{message}\" encountered:\n{new StackTrace()}");
			}
		}
	}
	[BepInPlugin("___Chinchi.ModBlame", "ModBlame", "1.0.0")]
	public class ModBlame : BaseUnityPlugin
	{
		public const string PluginGUID = "___Chinchi.ModBlame";

		public const string PluginAuthor = "___Chinchi";

		public const string PluginName = "ModBlame";

		public const string PluginVersion = "1.0.0";

		private void Awake()
		{
			Log.Init(((BaseUnityPlugin)this).Logger);
			PluginConfig.Init(((BaseUnityPlugin)this).Config);
			HookNoOrig.Init();
			ILCursorRemove.Init();
			LogSpam.Init();
		}

		private void Start()
		{
			if (Chainloader.PluginInfos.ContainsKey("com.rune580.riskofoptions"))
			{
				PluginConfig.RiskOfOptionsInit(((BaseUnityPlugin)this).Info.Location);
			}
		}
	}
	internal class PluginConfig
	{
		internal static ConfigFile file { get; private set; }

		internal static ConfigEntry<bool> noOrigEnabled { get; private set; }

		internal static ConfigEntry<string> noOrigWhitelist { get; private set; }

		internal static ConfigEntry<bool> cursorRemoveEnabled { get; private set; }

		internal static ConfigEntry<string> cursorRemoveWhitelist { get; private set; }

		internal static ConfigEntry<bool> logSpamEnabled { get; private set; }

		internal static ConfigEntry<string> logSpamText { get; private set; }

		internal static void Init(ConfigFile config)
		{
			file = config;
			noOrigEnabled = config.Bind<bool>("Hook No Orig", "Enable", true, "Whether hooks without calling orig will be reported.");
			noOrigWhitelist = config.Bind<string>("Hook No Orig", "Whitelist", "RoR2BepInExPack,MiscFixes,R2API.SkinIDRS.SetCustomIDRS,MultiplayerModTesting.Empty", "Comma-separated namespace/method name partials to be ignored. Regex matched.");
			cursorRemoveEnabled = config.Bind<bool>("ILCursor Remove", "Enable", true, "Whether hooks that use ILCursor.Remove and ILCursor.RemoveRange will be reported.");
			cursorRemoveWhitelist = config.Bind<string>("ILCursor Remove", "Whitelist", "RoR2BepInExPack,MiscFixes", "Comma-separated namespace/method name partials to be ignored. Regex matched.");
			logSpamEnabled = config.Bind<bool>("Log Spam", "Enable", true, "Whether blacklisted text will log the caller origin.");
			logSpamText = config.Bind<string>("Log Spam", "Text", "", "Comma-separated text to check for. Regex matched.");
		}

		[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
		internal static void RiskOfOptionsInit(string assemblyLocation)
		{
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Expected O, but got Unknown
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Expected O, but got Unknown
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Expected O, but got Unknown
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Expected O, but got Unknown
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Expected O, but got Unknown
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Expected O, but got Unknown
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected O, but got Unknown
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			FileInfo fileInfo = null;
			FileInfo[] files = new DirectoryInfo(Path.GetDirectoryName(assemblyLocation)).GetFiles("icon.png", SearchOption.TopDirectoryOnly);
			if (files != null && files.Length != 0)
			{
				fileInfo = files[0];
			}
			if (fileInfo != null)
			{
				string name = "ModBlameIcon";
				Texture2D val = new Texture2D(256, 256);
				((Object)val).name = name;
				if (ImageConversion.LoadImage(val, File.ReadAllBytes(fileInfo.FullName)))
				{
					Sprite obj = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f));
					((Object)obj).name = name;
					ModSettingsManager.SetModIcon(obj, "___Chinchi.ModBlame", "ModBlame");
				}
			}
			ModSettingsManager.SetModDescription("Find out which mods might be doing cursed stuff.");
			ModSettingsManager.AddOption((BaseOption)new CheckBoxOption(noOrigEnabled));
			ModSettingsManager.AddOption((BaseOption)new StringInputFieldOption(noOrigWhitelist));
			ModSettingsManager.AddOption((BaseOption)new CheckBoxOption(cursorRemoveEnabled));
			ModSettingsManager.AddOption((BaseOption)new StringInputFieldOption(cursorRemoveWhitelist));
			ModSettingsManager.AddOption((BaseOption)new CheckBoxOption(logSpamEnabled));
			ModSettingsManager.AddOption((BaseOption)new StringInputFieldOption(logSpamText));
		}
	}
	internal class Utils
	{
		internal static bool MatchPattern(string text, List<Regex> patterns)
		{
			return patterns.Any((Regex p) => p.IsMatch(text));
		}

		internal static string[] SplitText(string text, char separator = ',')
		{
			if (string.IsNullOrEmpty(text))
			{
				return Array.Empty<string>();
			}
			List<string> list = new List<string>();
			string[] array = text.Split(separator);
			foreach (string text2 in array)
			{
				if (!string.IsNullOrWhiteSpace(text2))
				{
					list.Add(text2.Trim());
				}
			}
			return list.ToArray();
		}
	}
}