Decompiled source of ModVerifier v1.0.0

plugins/com.github.Kirshoo.ModVerifier.dll

Decompiled 2 months ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Microsoft.CodeAnalysis;
using Mono.Cecil;
using Newtonsoft.Json;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("com.github.Kirshoo.ModVerifier")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("com.github.Kirshoo.ModVerifier")]
[assembly: AssemblyTitle("ModVerifier")]
[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 BepInEx
{
	[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
	[Conditional("CodeGeneration")]
	internal sealed class BepInAutoPluginAttribute : Attribute
	{
		public BepInAutoPluginAttribute(string? id = null, string? name = null, string? version = null)
		{
		}
	}
}
namespace BepInEx.Preloader.Core.Patching
{
	[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
	[Conditional("CodeGeneration")]
	internal sealed class PatcherAutoPluginAttribute : Attribute
	{
		public PatcherAutoPluginAttribute(string? id = null, string? name = null, string? version = null)
		{
		}
	}
}
namespace ModVerifier
{
	public enum SourceType
	{
		Web,
		LocalFile
	}
	public enum ReportPosition
	{
		TopLeft,
		Top,
		TopRight,
		Left,
		Right,
		BottomLeft,
		Bottom,
		BottomRight
	}
	[BepInPlugin("com.github.Kirshoo.ModVerifier", "ModVerifier", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		public class WhitelistEntry
		{
			public string guid = string.Empty;

			public string name = string.Empty;

			public string version = string.Empty;

			public string sha256 = string.Empty;
		}

		[CompilerGenerated]
		private sealed class <>c__DisplayClass19_0
		{
			public string fontname;

			internal bool <LoadFontCoroutine>b__0(TMP_FontAsset asset)
			{
				return ((Object)asset).name == fontname;
			}
		}

		[CompilerGenerated]
		private sealed class <LoadFontCoroutine>d__19 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public string fontname;

			private <>c__DisplayClass19_0 <>8__1;

			public Action<TMP_FontAsset> callback;

			private int <retries>5__2;

			private TMP_FontAsset <font>5__3;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <LoadFontCoroutine>d__19(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>8__1 = null;
				<font>5__3 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
				//IL_00fb: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>8__1 = new <>c__DisplayClass19_0();
					<>8__1.fontname = fontname;
					<retries>5__2 = 5;
					<font>5__3 = null;
					break;
				case 1:
					<>1__state = -1;
					break;
				}
				if (<retries>5__2 > 0)
				{
					<font>5__3 = ((IEnumerable<TMP_FontAsset>)Resources.FindObjectsOfTypeAll<TMP_FontAsset>()).FirstOrDefault((Func<TMP_FontAsset, bool>)((TMP_FontAsset asset) => ((Object)asset).name == <>8__1.fontname));
					if (!((Object)(object)<font>5__3 != (Object)null))
					{
						<retries>5__2--;
						Logger.LogDebug((object)$"Failed to load {<>8__1.fontname} font, retrying after {(float)(5 - <retries>5__2) * 0.05f}s...");
						<>2__current = (object)new WaitForSeconds((float)(5 - <retries>5__2) * 0.05f);
						<>1__state = 1;
						return true;
					}
					Logger.LogDebug((object)("Found " + <>8__1.fontname + "! Proceeding..."));
				}
				if ((Object)(object)<font>5__3 == (Object)null)
				{
					Logger.LogWarning((object)("Failed to load " + <>8__1.fontname + " font, fallback to built in font..."));
					<font>5__3 = Resources.GetBuiltinResource<TMP_FontAsset>("LiberationSans SDF");
				}
				callback?.Invoke(<font>5__3);
				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 const string WhitelistURL = "https://4ufps.github.io/peakverify.json";

		private static readonly string LogPath = Path.Combine(Paths.BepInExRootPath, "verifier_result.log");

		private static readonly StringBuilder LogBuffer = new StringBuilder();

		private const int MAX_LOAD_FONT_RETRIES = 5;

		private const float BACKOFF_DELAY_MILLISECONDS = 0.05f;

		private static int TotalAmount = 0;

		private static int Unauthorized = 0;

		private GameObject canvas_go;

		private TextMeshProUGUI report_content;

		private TMP_FontAsset font_asset;

		private ConfigEntry<ReportPosition> reportPos;

		private ConfigEntry<float> padding;

		public const string Id = "com.github.Kirshoo.ModVerifier";

		internal static ManualLogSource Logger { get; private set; } = null;


		public static string Name => "ModVerifier";

		public static string Version => "1.0.0";

		private async void Awake()
		{
			Logger = ((BaseUnityPlugin)this).Logger;
			BindConfig();
			CreateCanvas();
			Logger.LogInfo((object)"Starting verification...");
			try
			{
				bool flag = await VerifyRunningPlugins(SourceType.Web, "https://4ufps.github.io/peakverify.json");
				Logger.LogInfo((object)string.Format("Verification finished with {0} at {1:O}", flag ? "success" : "fail", DateTime.UtcNow));
				Logger.LogInfo((object)("Writing report into " + LogPath));
				File.WriteAllText(LogPath, LogBuffer.ToString());
				UpdateText(report_content, TotalAmount, Unauthorized);
			}
			catch (Exception ex)
			{
				Logger.LogError((object)("Verification failed: " + ex.Message));
				((TMP_Text)report_content).text = "Error making report:\n" + ex.Message;
			}
		}

		private void BindConfig()
		{
			reportPos = ((BaseUnityPlugin)this).Config.Bind<ReportPosition>("VerifierReport", "ReportPosition", ReportPosition.TopLeft, "The position of the mod verifier report.\r\nUse ReportPadding to make sure its not obstructed by other text.");
			padding = ((BaseUnityPlugin)this).Config.Bind<float>("VerifierReport", "ReportPadding", 20f, "Padding of the text from all sides.");
		}

		[IteratorStateMachine(typeof(<LoadFontCoroutine>d__19))]
		private IEnumerator LoadFontCoroutine(string fontname, Action<TMP_FontAsset> callback)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <LoadFontCoroutine>d__19(0)
			{
				fontname = fontname,
				callback = callback
			};
		}

		private void CreateCanvas()
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Expected O, but got Unknown
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Expected O, but got Unknown
			//IL_0125: Unknown result type (might be due to invalid IL or missing references)
			//IL_011d: Unknown result type (might be due to invalid IL or missing references)
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)canvas_go != (Object)null)
			{
				return;
			}
			canvas_go = new GameObject("ModVerifierCanvas");
			Canvas val = canvas_go.AddComponent<Canvas>();
			val.renderMode = (RenderMode)0;
			val.sortingOrder = 1000;
			CanvasScaler val2 = canvas_go.AddComponent<CanvasScaler>();
			val2.uiScaleMode = (ScaleMode)1;
			val2.referenceResolution = new Vector2(1920f, 1080f);
			GameObject val3 = new GameObject("VerifierReport");
			val3.transform.SetParent(canvas_go.transform, false);
			report_content = val3.AddComponent<TextMeshProUGUI>();
			((TMP_Text)report_content).text = "Loading report...";
			if ((Object)(object)font_asset != (Object)null)
			{
				SetFont(report_content, font_asset);
			}
			else
			{
				((MonoBehaviour)this).StartCoroutine(LoadFontCoroutine("DarumaDropOne-Regular SDF", delegate(TMP_FontAsset font)
				{
					font_asset = font;
					SetFont(report_content, font_asset);
				}));
			}
			TextAlignmentOptions alignment;
			switch (reportPos.Value)
			{
			case ReportPosition.Top:
			case ReportPosition.Bottom:
				alignment = (TextAlignmentOptions)514;
				break;
			case ReportPosition.TopLeft:
			case ReportPosition.Left:
			case ReportPosition.BottomLeft:
				alignment = (TextAlignmentOptions)513;
				break;
			case ReportPosition.TopRight:
			case ReportPosition.Right:
			case ReportPosition.BottomRight:
				alignment = (TextAlignmentOptions)516;
				break;
			default:
				alignment = (TextAlignmentOptions)513;
				break;
			}
			((TMP_Text)report_content).alignment = alignment;
			RectTransform component = val3.GetComponent<RectTransform>();
			PlaceInPosition(component, reportPos.Value, padding.Value);
			Object.DontDestroyOnLoad((Object)(object)canvas_go);
		}

		private void PlaceInPosition(RectTransform rect, ReportPosition pos, float padding)
		{
			//IL_0149: Unknown result type (might be due to invalid IL or missing references)
			//IL_0150: Unknown result type (might be due to invalid IL or missing references)
			//IL_0157: Unknown result type (might be due to invalid IL or missing references)
			//IL_015e: Unknown result type (might be due to invalid IL or missing references)
			Vector2 val = default(Vector2);
			Vector2 anchoredPosition = default(Vector2);
			switch (pos)
			{
			case ReportPosition.TopLeft:
				((Vector2)(ref val))..ctor(0f, 1f);
				((Vector2)(ref anchoredPosition))..ctor(padding, 0f - padding);
				break;
			case ReportPosition.Top:
				((Vector2)(ref val))..ctor(0.5f, 1f);
				((Vector2)(ref anchoredPosition))..ctor(0f, 0f - padding);
				break;
			case ReportPosition.TopRight:
				((Vector2)(ref val))..ctor(1f, 1f);
				((Vector2)(ref anchoredPosition))..ctor(0f - padding, 0f - padding);
				break;
			case ReportPosition.Left:
				((Vector2)(ref val))..ctor(0f, 0.5f);
				((Vector2)(ref anchoredPosition))..ctor(padding, 0f);
				break;
			case ReportPosition.Right:
				((Vector2)(ref val))..ctor(1f, 0.5f);
				((Vector2)(ref anchoredPosition))..ctor(0f - padding, 0f);
				break;
			case ReportPosition.BottomLeft:
				((Vector2)(ref val))..ctor(0f, 0f);
				((Vector2)(ref anchoredPosition))..ctor(padding, padding);
				break;
			case ReportPosition.Bottom:
				((Vector2)(ref val))..ctor(0.5f, 0f);
				((Vector2)(ref anchoredPosition))..ctor(0f, padding);
				break;
			case ReportPosition.BottomRight:
				((Vector2)(ref val))..ctor(1f, 0f);
				((Vector2)(ref anchoredPosition))..ctor(0f - padding, padding);
				break;
			default:
				((Vector2)(ref val))..ctor(0f, 1f);
				((Vector2)(ref anchoredPosition))..ctor(padding, 0f - padding);
				break;
			}
			rect.anchorMin = val;
			rect.anchorMax = val;
			rect.pivot = val;
			rect.anchoredPosition = anchoredPosition;
		}

		private static void UpdateText(TextMeshProUGUI textMesh, int total, int unauthorized)
		{
			string text = $"Running with a total of {total} mods";
			if (unauthorized > 0)
			{
				text += $"\nof which {unauthorized} are unauthorized";
			}
			((TMP_Text)textMesh).text = text;
		}

		private static void SetFont(TextMeshProUGUI textMesh, TMP_FontAsset font)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			((TMP_Text)textMesh).font = font;
			((TMP_Text)textMesh).fontSize = 20f;
			((TMP_Text)textMesh).overflowMode = (TextOverflowModes)0;
			((TMP_Text)textMesh).textWrappingMode = (TextWrappingModes)0;
			((Graphic)textMesh).color = Color.white;
			((TMP_Text)textMesh).outlineWidth = 0.075f;
			((TMP_Text)textMesh).outlineColor = Color32.op_Implicit(Color.black);
		}

		private async Task<bool> VerifyRunningPlugins(SourceType type, string source)
		{
			string pluginDir = Paths.PluginPath;
			LogBuffer.AppendLine("=== Verification Report (" + Plugin.Name + " v" + Version + ") ===");
			LogBuffer.AppendLine($"Timestamp: {DateTime.UtcNow:O}");
			LogBuffer.AppendLine($"Whitelist source: {source} of type {type}\n");
			Logger.LogDebug((object)("Fetching whitelist from " + source));
			WhitelistEntry[] source2;
			try
			{
				source2 = await FetchWhitelist(type, source);
			}
			catch (JsonSerializationException val)
			{
				JsonSerializationException innerException = val;
				throw new Exception("Serializing the whitelist: malformed whitelist", (Exception?)(object)innerException);
			}
			catch (HttpRequestException ex)
			{
				throw new Exception("Fetching the whitelist: " + ex.Message, ex);
			}
			string[] files = Directory.GetFiles(pluginDir, "*.dll", SearchOption.AllDirectories);
			foreach (string text in files)
			{
				if (!GetMetadata(text, out string guid, out string Name, out string version))
				{
					Logger.LogWarning((object)("Unable to retrieve metadata from " + text));
					LogBuffer.AppendLine("Unknown plugin: " + Path.GetFileNameWithoutExtension(text));
					continue;
				}
				TotalAmount++;
				string sha = ComputeSha256(text);
				bool flag = source2.Any((WhitelistEntry wMod) => string.Equals(wMod.guid, guid, StringComparison.OrdinalIgnoreCase) && string.Equals(wMod.version, version, StringComparison.OrdinalIgnoreCase) && string.Equals(wMod.sha256, sha, StringComparison.OrdinalIgnoreCase));
				LogBuffer.AppendLine(Name + " (" + guid + ") v" + version);
				LogBuffer.AppendLine("    SHA256: " + sha);
				LogBuffer.AppendLine("    Status: " + (flag ? "Allowed" : "Unauthorized!"));
				if (!flag)
				{
					Unauthorized++;
					Logger.LogWarning((object)("Unauthorized mod: " + Name + " (" + version + ")"));
				}
			}
			return Unauthorized == 0;
		}

		private async Task<WhitelistEntry[]> FetchWhitelist(SourceType type, string source)
		{
			return type switch
			{
				SourceType.Web => await FetchWhitelistAsync(source), 
				SourceType.LocalFile => await LoadWhitelistFileAsync(source), 
				_ => throw new InvalidDataException($"Unknown source type: {type}"), 
			};
		}

		private async Task<WhitelistEntry[]> FetchWhitelistAsync(string web_url)
		{
			using HttpClient client = new HttpClient();
			return JsonConvert.DeserializeObject<WhitelistEntry[]>(await client.GetStringAsync(web_url));
		}

		private async Task<WhitelistEntry[]> LoadWhitelistFileAsync(string filepath)
		{
			return JsonConvert.DeserializeObject<WhitelistEntry[]>(await File.ReadAllTextAsync(filepath));
		}

		private static bool GetMetadata(string filepath, out string GUID, out string Name, out string Version)
		{
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: 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_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			ModuleDefinition val = ModuleDefinition.ReadModule(filepath);
			GUID = (Name = (Version = ""));
			TypeDefinition val2 = ((IEnumerable<TypeDefinition>)val.Types).FirstOrDefault((Func<TypeDefinition, bool>)((TypeDefinition t) => ((IEnumerable<CustomAttribute>)t.CustomAttributes).Any((CustomAttribute a) => ((MemberReference)a.AttributeType).FullName == "BepInEx.BepInPlugin")));
			if (val2 == null)
			{
				return false;
			}
			CustomAttribute val3 = ((IEnumerable<CustomAttribute>)val2.CustomAttributes).First((CustomAttribute a) => ((MemberReference)a.AttributeType).FullName == "BepInEx.BepInPlugin");
			CustomAttributeArgument val4 = val3.ConstructorArguments[0];
			GUID = ((CustomAttributeArgument)(ref val4)).Value?.ToString() ?? string.Empty;
			val4 = val3.ConstructorArguments[1];
			Name = ((CustomAttributeArgument)(ref val4)).Value?.ToString() ?? string.Empty;
			val4 = val3.ConstructorArguments[2];
			Version = ((CustomAttributeArgument)(ref val4)).Value?.ToString() ?? string.Empty;
			return true;
		}

		private static string ComputeSha256(string filepath)
		{
			using SHA256 sHA = SHA256.Create();
			using FileStream inputStream = File.OpenRead(filepath);
			byte[] array = sHA.ComputeHash(inputStream);
			return BitConverter.ToString(array).Replace("-", "").ToUpperInvariant();
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}