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 ModVerifier.Common;
using ModVerifier.Configuration;
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.1.1.0")]
[assembly: AssemblyInformationalVersion("1.1.1")]
[assembly: AssemblyProduct("com.github.Kirshoo.ModVerifier")]
[assembly: AssemblyTitle("ModVerifier")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.1.1.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[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]
[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]
[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 interface IHashFunc
{
byte[] Hash(Stream input);
}
internal class ModScanner
{
private IHashFunc hash;
public ModScanner(IHashFunc hashfunc)
{
hash = hashfunc;
}
public ModMetadata GetMetadata(string filepath)
{
//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_00ac: Unknown result type (might be due to invalid IL or missing references)
//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
ModuleDefinition val = ModuleDefinition.ReadModule(filepath);
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)
{
throw new ArgumentException("file is not a valid bepinex plugin file");
}
CustomAttribute val3 = ((IEnumerable<CustomAttribute>)val2.CustomAttributes).First((CustomAttribute a) => ((MemberReference)a.AttributeType).FullName == "BepInEx.BepInPlugin");
ModMetadata result = new ModMetadata();
CustomAttributeArgument val4 = val3.ConstructorArguments[0];
result.GUID = ((CustomAttributeArgument)(ref val4)).Value?.ToString() ?? string.Empty;
val4 = val3.ConstructorArguments[1];
result.Name = ((CustomAttributeArgument)(ref val4)).Value?.ToString() ?? string.Empty;
val4 = val3.ConstructorArguments[2];
result.Version = ((CustomAttributeArgument)(ref val4)).Value?.ToString() ?? string.Empty;
result.SHA256 = ComputeHash(filepath);
return result;
}
public bool TryGetMetadata(string filepath, out ModMetadata metadata)
{
metadata = new ModMetadata();
try
{
metadata = GetMetadata(filepath);
return true;
}
catch (ArgumentException)
{
return false;
}
}
private string ComputeHash(string filepath)
{
using FileStream input = File.OpenRead(filepath);
byte[] array = hash.Hash(input);
return BitConverter.ToString(array).Replace("-", "").ToUpperInvariant();
}
}
public enum SourceType
{
Web,
LocalFile
}
[BepInPlugin("com.github.Kirshoo.ModVerifier", "ModVerifier", "1.1.1")]
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;
}
private const string WhitelistURL = "https://kirshoo.github.io/peakverify.json";
private static readonly string LogPath = Path.Combine(Paths.BepInExRootPath, "verifier_result.log");
private static readonly StringBuilder LogBuffer = new StringBuilder();
private static int Unauthorized = 0;
private GameObject manager_go;
private VerifierReportManager ReportManager;
private ModScanner scanner = new ModScanner(new ShaHasher());
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.1.1";
private async void Awake()
{
Logger = ((BaseUnityPlugin)this).Logger;
BindConfig();
CreateManager();
Logger.LogInfo((object)"Starting verification...");
try
{
bool flag = await VerifyRunningPlugins(SourceType.Web, "https://kirshoo.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(Unauthorized);
}
catch (Exception ex)
{
Logger.LogError((object)("Verification failed: " + ex.Message));
ReportManager.SetReportError(ex.Message);
}
Logger.LogInfo((object)(Name + " was successfully loaded"));
}
private void BindConfig()
{
VerifierReport.BindConfig(((BaseUnityPlugin)this).Config);
}
private void CreateManager()
{
//IL_0015: Unknown result type (might be due to invalid IL or missing references)
//IL_001f: Expected O, but got Unknown
if (!((Object)(object)manager_go != (Object)null))
{
manager_go = new GameObject("VerifierReportManager");
ReportManager = manager_go.AddComponent<VerifierReportManager>();
ReportManager.loadedMods = GetLoadedMods();
ReportManager.timestamp = DateTime.UtcNow;
Object.DontDestroyOnLoad((Object)(object)manager_go);
}
}
private void UpdateText(int unauthorized)
{
ReportManager.SetReportText(unauthorized);
}
private async Task<bool> VerifyRunningPlugins(SourceType type, string source)
{
_ = Paths.PluginPath;
LogBuffer.AppendLine("=== Verification Report (" + 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 ex)
{
JsonSerializationException innerException = ex;
throw new Exception("Serializing the whitelist: malformed whitelist", (Exception?)(object)innerException);
}
catch (HttpRequestException ex2)
{
throw new Exception("Fetching the whitelist: " + ex2.Message, ex2);
}
foreach (ModMetadata metadata in ReportManager.loadedMods)
{
bool flag = source2.Any((WhitelistEntry wMod) => string.Equals(wMod.guid, metadata.GUID, StringComparison.OrdinalIgnoreCase) && string.Equals(wMod.version, metadata.Version, StringComparison.OrdinalIgnoreCase) && string.Equals(wMod.sha256, metadata.SHA256, StringComparison.OrdinalIgnoreCase));
LogBuffer.AppendLine(metadata.Name + " (" + metadata.GUID + ") v" + metadata.Version);
LogBuffer.AppendLine(" SHA256: " + metadata.SHA256);
LogBuffer.AppendLine(" Status: " + (flag ? "Allowed" : "Unauthorized!"));
if (!flag)
{
Unauthorized++;
Logger.LogWarning((object)("Unauthorized mod: " + metadata.Name + " (" + metadata.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 List<ModMetadata> GetLoadedMods()
{
List<ModMetadata> list = new List<ModMetadata>();
string[] files = Directory.GetFiles(Paths.PluginPath, "*.dll", SearchOption.AllDirectories);
foreach (string text in files)
{
if (!scanner.TryGetMetadata(text, out var metadata))
{
Logger.LogWarning((object)("Unable to retrieve metadata from " + text));
}
else
{
list.Add(metadata);
}
}
return list;
}
}
public class ShaHasher : IHashFunc
{
private SHA256 hashfunc;
public ShaHasher()
{
hashfunc = SHA256.Create();
}
public byte[] Hash(Stream input)
{
return hashfunc.ComputeHash(input);
}
}
public class Utils
{
public struct FontLoader
{
public int MAX_LOAD_FONT_RETRIES;
public float BACKOFF_DELAY_MILLISECONDS;
public FontLoader()
{
MAX_LOAD_FONT_RETRIES = 5;
BACKOFF_DELAY_MILLISECONDS = 0.05f;
}
public IEnumerator LoadFontCoroutine(string fontname, Action<TMP_FontAsset> callback)
{
int retries = MAX_LOAD_FONT_RETRIES;
TMP_FontAsset font = null;
while (retries > 0)
{
font = ((IEnumerable<TMP_FontAsset>)Resources.FindObjectsOfTypeAll<TMP_FontAsset>()).FirstOrDefault((Func<TMP_FontAsset, bool>)((TMP_FontAsset asset) => ((Object)asset).name == fontname));
if ((Object)(object)font != (Object)null)
{
Plugin.Logger.LogDebug((object)("Found " + fontname + "! Proceeding..."));
break;
}
retries--;
Plugin.Logger.LogDebug((object)$"Failed to load {fontname} font, retrying after {(float)(MAX_LOAD_FONT_RETRIES - retries) * BACKOFF_DELAY_MILLISECONDS}s...");
yield return (object)new WaitForSeconds((float)(MAX_LOAD_FONT_RETRIES - retries) * BACKOFF_DELAY_MILLISECONDS);
}
if ((Object)(object)font == (Object)null)
{
Plugin.Logger.LogWarning((object)("Failed to load " + fontname + " font, fallback to built in font..."));
font = Resources.GetBuiltinResource<TMP_FontAsset>("LiberationSans SDF");
}
callback?.Invoke(font);
}
}
}
internal class VerifierReportManager : MonoBehaviour
{
private readonly TextMeshProUGUI report_textblock;
private readonly TextMeshProUGUI modcycle_textblock;
internal List<ModMetadata> loadedMods;
private int modIndex;
private const float FADE_DURATION = 0.3f;
private const float CYCLE_INTERVAL = 2f;
internal DateTime timestamp;
private static TMP_FontAsset? FONT_ASSET;
public Utils.FontLoader fontLoader = new Utils.FontLoader();
public VerifierReportManager()
{
//IL_004d: Unknown result type (might be due to invalid IL or missing references)
//IL_005c: Unknown result type (might be due to invalid IL or missing references)
//IL_0062: Expected O, but got Unknown
//IL_0095: Unknown result type (might be due to invalid IL or missing references)
//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
//IL_00cd: Expected O, but got Unknown
//IL_0108: Unknown result type (might be due to invalid IL or missing references)
//IL_0117: Unknown result type (might be due to invalid IL or missing references)
//IL_011e: Expected O, but got Unknown
//IL_0159: Unknown result type (might be due to invalid IL or missing references)
Canvas val = ((Component)this).gameObject.AddComponent<Canvas>();
val.renderMode = (RenderMode)0;
val.sortingOrder = 1000;
CanvasScaler val2 = ((Component)this).gameObject.AddComponent<CanvasScaler>();
val2.uiScaleMode = (ScaleMode)1;
val2.referenceResolution = new Vector2(1920f, 1080f);
GameObject val3 = new GameObject("VerifierPanel");
val3.transform.SetParent(((Component)this).gameObject.transform, false);
val3.AddComponent<RectTransform>();
PlaceInPosition(val3.GetComponent<RectTransform>(), VerifierReport.Position, new Vector2(VerifierReport.PaddingLeft, VerifierReport.PaddingBottom));
VerticalLayoutGroup val4 = val3.AddComponent<VerticalLayoutGroup>();
((HorizontalOrVerticalLayoutGroup)val4).spacing = 10f;
((LayoutGroup)val4).childAlignment = PositionToTextAnchor(VerifierReport.Position);
GameObject val5 = new GameObject("VerifierReport");
val5.transform.SetParent(val3.transform, false);
report_textblock = val5.AddComponent<TextMeshProUGUI>();
((TMP_Text)report_textblock).text = "Loading report...";
((TMP_Text)report_textblock).alignment = PositionToAlignment(VerifierReport.Position);
GameObject val6 = new GameObject("ModCycle");
val6.transform.SetParent(val3.transform, false);
modcycle_textblock = val6.AddComponent<TextMeshProUGUI>();
((TMP_Text)modcycle_textblock).text = "";
((TMP_Text)modcycle_textblock).alignment = PositionToAlignment(VerifierReport.Position);
loadedMods = new List<ModMetadata>();
timestamp = DateTime.UtcNow;
}
private void Start()
{
LoadFont();
((MonoBehaviour)this).StartCoroutine(Cycle());
}
private void LoadFont()
{
if ((Object)(object)FONT_ASSET != (Object)null)
{
SetFont(report_textblock, FONT_ASSET);
SetFont(modcycle_textblock, FONT_ASSET);
return;
}
((MonoBehaviour)this).StartCoroutine(fontLoader.LoadFontCoroutine("DarumaDropOne-Regular SDF", delegate(TMP_FontAsset font)
{
FONT_ASSET = font;
SetFont(report_textblock, FONT_ASSET);
SetFont(modcycle_textblock, FONT_ASSET);
}));
}
public void SetReportText(int unauthorized)
{
((TMP_Text)report_textblock).text = $"Verification Time: {timestamp}\r\nMods: {loadedMods.Count - unauthorized}/{loadedMods.Count} allowed";
}
public void SetReportError(string error)
{
((TMP_Text)report_textblock).text = "Error during verification:\r\n" + error;
}
private IEnumerator Cycle()
{
while (true)
{
yield return (object)new WaitForSecondsRealtime(2f);
yield return Fade(1f, 0f);
ModMetadata modMetadata = loadedMods[modIndex];
((TMP_Text)modcycle_textblock).text = $"[{modIndex + 1}/{loadedMods.Count}] {modMetadata.Name} v{modMetadata.Version} ({modMetadata.SHA256.Substring(0, 8)}...)";
modIndex = (modIndex + 1) % loadedMods.Count;
yield return Fade(0f, 1f);
}
}
private IEnumerator Fade(float from, float to)
{
float t = 0f;
Color c = ((Graphic)modcycle_textblock).color;
while (t < 0.3f)
{
t += Time.deltaTime;
float num = Mathf.Lerp(from, to, t / 0.3f);
((Graphic)modcycle_textblock).color = new Color(c.r, c.g, c.b, num);
yield return null;
}
}
public static TextAnchor PositionToTextAnchor(ReportPosition position)
{
return (TextAnchor)(position switch
{
ReportPosition.Top => 1,
ReportPosition.Bottom => 7,
ReportPosition.Left => 3,
ReportPosition.TopLeft => 0,
ReportPosition.BottomLeft => 6,
ReportPosition.Right => 5,
ReportPosition.TopRight => 2,
ReportPosition.BottomRight => 8,
_ => 3,
});
}
public static TextAlignmentOptions PositionToAlignment(ReportPosition position)
{
switch (position)
{
case ReportPosition.Top:
case ReportPosition.Bottom:
return (TextAlignmentOptions)514;
case ReportPosition.Left:
case ReportPosition.TopLeft:
case ReportPosition.BottomLeft:
return (TextAlignmentOptions)513;
case ReportPosition.Right:
case ReportPosition.TopRight:
case ReportPosition.BottomRight:
return (TextAlignmentOptions)516;
default:
return (TextAlignmentOptions)513;
}
}
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 static void PlaceInPosition(RectTransform rect, ReportPosition pos, Vector2 padding)
{
//IL_00db: Unknown result type (might be due to invalid IL or missing references)
//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
Vector2 val = default(Vector2);
switch (pos)
{
case ReportPosition.TopLeft:
((Vector2)(ref val))..ctor(0f, 1f);
break;
case ReportPosition.Top:
((Vector2)(ref val))..ctor(0.5f, 1f);
break;
case ReportPosition.TopRight:
((Vector2)(ref val))..ctor(1f, 1f);
break;
case ReportPosition.Left:
((Vector2)(ref val))..ctor(0f, 0.5f);
break;
case ReportPosition.Right:
((Vector2)(ref val))..ctor(1f, 0.5f);
break;
case ReportPosition.BottomLeft:
((Vector2)(ref val))..ctor(0f, 0f);
break;
case ReportPosition.Bottom:
((Vector2)(ref val))..ctor(0.5f, 0f);
break;
case ReportPosition.BottomRight:
((Vector2)(ref val))..ctor(1f, 0f);
break;
default:
((Vector2)(ref val))..ctor(0f, 1f);
break;
}
rect.anchorMin = val;
rect.anchorMax = val;
rect.pivot = val;
rect.anchoredPosition = padding;
}
}
}
namespace ModVerifier.Configuration
{
internal static class VerifierReport
{
private static ConfigEntry<ReportPosition>? _reportPos;
private static ConfigEntry<float>? _reportPaddingLeft;
private static ConfigEntry<float>? _reportPaddingBottom;
public static ReportPosition Position
{
get
{
if (_reportPos == null)
{
Plugin.Logger.LogError((object)"Requested config entry, but no config file was bound.");
throw new InvalidOperationException("requesting config value before any config file was bound");
}
return _reportPos.Value;
}
}
public static float PaddingLeft
{
get
{
if (_reportPaddingLeft == null)
{
Plugin.Logger.LogError((object)"Requested config entry, but no config file was bound.");
throw new InvalidOperationException("requesting config value before any config file was bound");
}
return _reportPaddingLeft.Value;
}
}
public static float PaddingBottom
{
get
{
if (_reportPaddingBottom == null)
{
Plugin.Logger.LogError((object)"Requested config entry, but no config file was bound.");
throw new InvalidOperationException("requesting config value before any config file was bound");
}
return _reportPaddingBottom.Value;
}
}
public static void BindConfig(ConfigFile config)
{
_reportPos = config.Bind<ReportPosition>("VerifierReport", "ReportPosition", ReportPosition.Right, "The position of the mod verifier report.\r\nUse ReportPadding to make sure its not obstructed by other text.");
_reportPaddingLeft = config.Bind<float>("VerifierReport", "ReportMoveRight", -20f, "Moves report right from its position in pixels.\r\nSet to negative values to move left instead.");
_reportPaddingBottom = config.Bind<float>("VerifierReport", "ReportMoveUp", 0f, "Moves report upwards from its position in pixels.\r\nSet to negative values to move downwards instead.");
}
}
}
namespace ModVerifier.Common
{
public class ExceptionNotification : Exception
{
private readonly List<Exception> innerExceptions = new List<Exception>();
public override string Message => base.Message + string.Format(", {0} inner exceptions: {1}", innerExceptions.Count, string.Join(", ", innerExceptions));
public void AddException(Exception exception)
{
innerExceptions.Add(exception);
}
public bool HasErrors()
{
return innerExceptions.Count > 0;
}
}
internal struct ModMetadata
{
public string Name;
public string GUID;
public string Version;
public string SHA256;
public ModMetadata()
{
Name = string.Empty;
GUID = string.Empty;
Version = string.Empty;
SHA256 = string.Empty;
}
}
public enum ReportPosition
{
Top,
Bottom,
Left,
TopLeft,
BottomLeft,
Right,
TopRight,
BottomRight
}
}
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
internal sealed class IgnoresAccessChecksToAttribute : Attribute
{
public IgnoresAccessChecksToAttribute(string assemblyName)
{
}
}
}