Decompiled source of YummiHarness v0.2.3

YummiHarness.dll

Decompiled 5 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
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 BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Collections.Generic;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
[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 YummiHarness
{
	public sealed class ConfigMigrationContext
	{
		internal ConfigFile Config;

		internal int FromVersion;

		internal int ToVersion;

		internal readonly Dictionary<(string section, string key), object> Transferred = new Dictionary<(string, string), object>();

		public void Remove(string section, string key)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Expected O, but got Unknown
			Config.Remove(new ConfigDefinition(section, key));
		}

		public bool MigrateKey<T>(string fromSection, string fromKey, string toSection, string toKey)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Expected O, but got Unknown
			ConfigDefinition val = new ConfigDefinition(fromSection, fromKey);
			ConfigEntry<T> val2 = default(ConfigEntry<T>);
			if (!Config.TryGetEntry<T>(val, ref val2))
			{
				return false;
			}
			Transferred[(toSection, toKey)] = val2.Value;
			Config.Remove(val);
			return true;
		}

		public void ApplyTransferred<T>(string section, string key, ConfigEntry<T> entry)
		{
			if (Transferred.TryGetValue((section, key), out var value) && value is T value2)
			{
				entry.Value = value2;
			}
		}
	}
	public sealed class ConfigSchemaOptions
	{
		public int? WipeIfBelow;

		public Action<ConfigMigrationContext> Upgrade;
	}
	public static class Harness
	{
		public const string ConfigSchemaSection = "_ConfigSchema";

		public const string ConfigSchemaVersionKey = "Version";

		internal static readonly List<PatchFailure> Failures = new List<PatchFailure>();

		internal static readonly List<ConflictRecord> Conflicts = new List<ConflictRecord>();

		internal static readonly List<CompatibilityAdvisory> Advisories = new List<CompatibilityAdvisory>();

		internal static readonly List<MethodBase> Targets = new List<MethodBase>();

		internal static readonly Dictionary<string, string> ModNames = new Dictionary<string, string>();

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

		private static readonly Dictionary<string, ModuleDefinition> modules = new Dictionary<string, ModuleDefinition>();

		internal static Action<string> LogWarn = delegate
		{
		};

		internal static Action<string> LogInfo = delegate
		{
		};

		internal static readonly List<NoticeRecord> Notices = new List<NoticeRecord>();

		private static string SchemaVersionDesc => Lang.En("Config file format version managed by YummiHarness.ApplyConfigSchema. Do not edit unless you know what you are doing.").ZhCN("由 YummiHarness.ApplyConfigSchema 维护的配置格式版本,不懂请勿手改。");

		internal static int ChangeCount { get; private set; }

		internal static DateTime LastChangeUtc { get; private set; } = DateTime.UtcNow;

		public static ConfigMigrationContext ApplyConfigSchema(ConfigFile config, int currentVersion, ConfigSchemaOptions options)
		{
			if (config == null)
			{
				throw new ArgumentNullException("config");
			}
			if (currentVersion < 1)
			{
				throw new ArgumentOutOfRangeException("currentVersion");
			}
			ConfigEntry<int> val = config.Bind<int>("_ConfigSchema", "Version", 0, SchemaVersionDesc);
			int num = val.Value;
			ConfigMigrationContext configMigrationContext = null;
			if (options != null && options.WipeIfBelow.HasValue && num < options.WipeIfBelow.Value)
			{
				WipeConfigExceptSchemaVersion(config);
				num = 0;
			}
			else if (options != null && options.Upgrade != null && num < currentVersion)
			{
				configMigrationContext = new ConfigMigrationContext
				{
					Config = config,
					FromVersion = num,
					ToVersion = currentVersion
				};
				options.Upgrade(configMigrationContext);
			}
			if (num != currentVersion)
			{
				val.Value = currentVersion;
			}
			return configMigrationContext;
		}

		private static void WipeConfigExceptSchemaVersion(ConfigFile config)
		{
			foreach (ConfigDefinition item in config.Keys.ToList())
			{
				if (!(item.Section == "_ConfigSchema") || !(item.Key == "Version"))
				{
					config.Remove(item);
				}
			}
		}

		public static void PruneConfigKeys(ConfigFile config, IEnumerable<ConfigDefinition> retain)
		{
			if (config == null)
			{
				throw new ArgumentNullException("config");
			}
			if (retain == null)
			{
				throw new ArgumentNullException("retain");
			}
			HashSet<ConfigDefinition> hashSet = new HashSet<ConfigDefinition>(retain);
			foreach (ConfigDefinition item in config.Keys.ToList())
			{
				if (!hashSet.Contains(item))
				{
					config.Remove(item);
				}
			}
		}

		public static void PruneConfigKeys(ConfigFile config, params ConfigDefinition[] retain)
		{
			PruneConfigKeys(config, (IEnumerable<ConfigDefinition>)retain);
		}

		private static void Changed()
		{
			ChangeCount++;
			LastChangeUtc = DateTime.UtcNow;
		}

		public static void PatchAll(Harmony harmony, Type patchClass, PatchOptions options = null)
		{
			string id = harmony.Id;
			string text = options?.ModName ?? id;
			ModNames[id] = text;
			Changed();
			List<HarmonyMethod> classAttrs = HarmonyMethodExtensions.GetFromType(patchClass) ?? new List<HarmonyMethod>();
			MethodInfo[] methods = patchClass.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (MethodInfo methodInfo in methods)
			{
				List<HarmonyMethod> fromMethod = HarmonyMethodExtensions.GetFromMethod((MethodBase)methodInfo);
				if (fromMethod != null && fromMethod.Count != 0)
				{
					PatchOne(harmony, text, options, patchClass, methodInfo, classAttrs, fromMethod);
				}
			}
			Changed();
		}

		public static void RegisterConflict(string modName, string otherGuid, string otherName = null, string note = null)
		{
			if (!Conflicts.Any((ConflictRecord c) => c.Mod == modName && c.OtherGuid == otherGuid))
			{
				Conflicts.Add(new ConflictRecord
				{
					Mod = modName,
					OtherGuid = otherGuid,
					OtherName = (otherName ?? otherGuid),
					Note = note
				});
				Changed();
			}
		}

		public static void RegisterCompatibilityAdvisory(string reporterModName, string[] whenAllPluginsLoaded, string summaryZh, string summaryEn, string detailZh = null, string detailEn = null, string advisoryId = null, NoticeLevel level = NoticeLevel.Warning)
		{
			if (string.IsNullOrEmpty(reporterModName) || whenAllPluginsLoaded == null || whenAllPluginsLoaded.Length == 0)
			{
				LogWarn("[YummiHarness] RegisterCompatibilityAdvisory: reporterModName and whenAllPluginsLoaded are required.");
				return;
			}
			if (string.IsNullOrEmpty(summaryZh) && string.IsNullOrEmpty(summaryEn))
			{
				LogWarn("[YummiHarness] RegisterCompatibilityAdvisory: summaryZh or summaryEn is required.");
				return;
			}
			string dedupe = advisoryId;
			if (string.IsNullOrEmpty(dedupe))
			{
				dedupe = reporterModName + "|" + string.Join(",", whenAllPluginsLoaded.OrderBy<string, string>((string g) => g, StringComparer.Ordinal));
			}
			if (!Advisories.Any((CompatibilityAdvisory a) => a.DedupeKey == dedupe))
			{
				Advisories.Add(new CompatibilityAdvisory
				{
					DedupeKey = dedupe,
					ReporterMod = reporterModName,
					Level = level,
					RequiredPluginGuids = whenAllPluginsLoaded.Where((string g) => !string.IsNullOrEmpty(g)).Distinct().ToArray(),
					SummaryZh = (summaryZh ?? summaryEn),
					SummaryEn = (summaryEn ?? summaryZh),
					DetailZh = detailZh,
					DetailEn = detailEn
				});
				Changed();
			}
		}

		public static void Report(NoticeLevel level, string modName, string summaryZh, string summaryEn, string detailZh = null, string detailEn = null)
		{
			if (string.IsNullOrEmpty(modName) || (string.IsNullOrEmpty(summaryZh) && string.IsNullOrEmpty(summaryEn)))
			{
				LogWarn("[YummiHarness] Report: modName and summaryZh or summaryEn are required.");
				return;
			}
			Notices.Add(new NoticeRecord
			{
				Level = level,
				Mod = modName,
				SummaryZh = (summaryZh ?? summaryEn),
				SummaryEn = (summaryEn ?? summaryZh),
				DetailZh = detailZh,
				DetailEn = detailEn
			});
			Changed();
		}

		internal static bool AllPluginsLoaded(string[] guids)
		{
			if (guids == null || guids.Length == 0)
			{
				return false;
			}
			for (int i = 0; i < guids.Length; i++)
			{
				if (!Chainloader.PluginInfos.ContainsKey(guids[i]))
				{
					return false;
				}
			}
			return true;
		}

		public static bool IsPluginLoaded(string guid)
		{
			return Chainloader.PluginInfos.ContainsKey(guid);
		}

		public static void DeclareCompatible(string harmonyIdA, string harmonyIdB)
		{
			compatible.Add(PairKey(harmonyIdA, harmonyIdB));
			Changed();
		}

		public static void ReportFailure(string modName, string what, string reasonZh, string reasonEn)
		{
			Failures.Add(new PatchFailure
			{
				Mod = modName,
				Patch = what,
				Target = "-",
				ReasonZh = reasonZh,
				ReasonEn = reasonEn
			});
			Changed();
		}

		private static void PatchOne(Harmony harmony, string modName, PatchOptions options, Type patchClass, MethodInfo patch, List<HarmonyMethod> classAttrs, List<HarmonyMethod> methodAttrs)
		{
			//IL_02a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a7: Expected O, but got Unknown
			string text = patchClass.Name + "." + patch.Name;
			string target = "?";
			try
			{
				MethodBase methodBase = ResolveTarget(HarmonyMethod.Merge(classAttrs.Concat(methodAttrs).ToList()));
				if (methodBase == null)
				{
					Fail(options, modName, text, target, "找不到目标方法(可能被改名或签名变化)", "Target method not found (renamed or signature changed)");
					return;
				}
				target = methodBase.DeclaringType?.Name + "." + methodBase.Name;
				if (!(patch.GetCustomAttributes(typeof(GuardAttribute), inherit: false).FirstOrDefault() is GuardAttribute guardAttribute))
				{
					LogWarn("[YummiHarness] [" + modName + "] " + Lang.En(text + " has no [Guard]; applied without verification").ZhCN(text + " 没有 [Guard],不做校验直接应用"));
				}
				else
				{
					string error;
					string text2 = ComputeHash(methodBase, out error);
					if (text2 == null)
					{
						Fail(options, modName, text, target, "无法计算目标方法指纹:" + error, "Cannot compute target fingerprint: " + error);
						return;
					}
					if (!string.Equals(text2, guardAttribute.Hash, StringComparison.OrdinalIgnoreCase))
					{
						Fail(options, modName, text, target, "目标方法与原版不一致(期望 " + guardAttribute.Hash + ",当前 " + text2 + "),已跳过", "Target differs from vanilla (expected " + guardAttribute.Hash + ", got " + text2 + "); skipped");
						return;
					}
				}
				List<string> list = (from o in OwnersOf(methodBase)
					where o != harmony.Id && !IsCompatible(o, harmony.Id)
					select o).ToList();
				if (list.Count > 0 && options != null && options.OnShared != null)
				{
					SharedTarget arg = new SharedTarget
					{
						Mod = modName,
						Target = target,
						Others = list.Select(DisplayName).ToList()
					};
					bool flag = true;
					try
					{
						flag = options.OnShared(arg);
					}
					catch (Exception ex)
					{
						LogWarn("[YummiHarness] OnShared: " + ex.Message);
					}
					if (!flag)
					{
						LogInfo("[YummiHarness] [" + modName + "] " + Lang.En(text + " skipped by caller because the target is already patched by another mod").ZhCN(text + " 因目标已被其他 mod 修改而按调用方要求跳过"));
						return;
					}
				}
				HarmonyMethod val = new HarmonyMethod(patch);
				switch (Kind(patch))
				{
				case "prefix":
					harmony.Patch(methodBase, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					break;
				case "postfix":
					harmony.Patch(methodBase, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					break;
				case "transpiler":
					harmony.Patch(methodBase, (HarmonyMethod)null, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null);
					break;
				case "finalizer":
					harmony.Patch(methodBase, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, val, (HarmonyMethod)null);
					break;
				default:
					Fail(options, modName, text, target, "无法判断补丁类型(请加 [HarmonyPrefix] 等特性)", "Cannot tell patch kind (add [HarmonyPrefix] etc.)");
					return;
				}
				if (!Targets.Contains(methodBase))
				{
					Targets.Add(methodBase);
				}
			}
			catch (Exception ex2)
			{
				Fail(options, modName, text, target, ex2.GetType().Name + ": " + ex2.Message, ex2.GetType().Name + ": " + ex2.Message);
			}
		}

		private static void Fail(PatchOptions options, string mod, string patch, string target, string zh, string en)
		{
			PatchFailure patchFailure = new PatchFailure
			{
				Mod = mod,
				Patch = patch,
				Target = target,
				ReasonZh = zh,
				ReasonEn = en
			};
			bool flag = true;
			if (options != null && options.OnFailure != null)
			{
				try
				{
					flag = options.OnFailure(patchFailure);
				}
				catch (Exception ex)
				{
					LogWarn("[YummiHarness] OnFailure: " + ex.Message);
				}
			}
			if (flag)
			{
				Failures.Add(patchFailure);
			}
			LogWarn(string.Concat("[YummiHarness] [", mod, "] ", Lang.En("patch not applied ").ZhCN("patch 未应用 "), patch, " -> ", target, ": ", patchFailure.Reason));
		}

		internal static List<string> OwnersOf(MethodBase method)
		{
			Patches patchInfo = Harmony.GetPatchInfo(method);
			if (patchInfo == null)
			{
				return new List<string>();
			}
			return (from p in patchInfo.Prefixes.Concat(patchInfo.Postfixes).Concat(patchInfo.Transpilers).Concat(patchInfo.Finalizers)
				select p.owner).Distinct().ToList();
		}

		internal static string DisplayName(string harmonyId)
		{
			if (!ModNames.TryGetValue(harmonyId, out var value))
			{
				return harmonyId;
			}
			return value;
		}

		private static string PairKey(string a, string b)
		{
			if (string.CompareOrdinal(a, b) > 0)
			{
				return b + "|" + a;
			}
			return a + "|" + b;
		}

		private static bool IsCompatible(string a, string b)
		{
			return compatible.Contains(PairKey(a, b));
		}

		internal static List<SharedTarget> ComputeShared()
		{
			List<SharedTarget> list = new List<SharedTarget>();
			foreach (MethodBase target in Targets)
			{
				Patches patchInfo = Harmony.GetPatchInfo(target);
				if (patchInfo == null)
				{
					continue;
				}
				List<string> list2 = (from o in OwnersOf(target)
					where o != "com.yummi.dsp.yummiharness"
					select o).ToList();
				bool flag = false;
				bool flag2 = false;
				for (int num = 0; num < list2.Count; num++)
				{
					for (int num2 = num + 1; num2 < list2.Count; num2++)
					{
						if (!IsCompatible(list2[num], list2[num2]))
						{
							flag = true;
							flag2 |= IsRiskyPair(patchInfo, list2[num], list2[num2]);
						}
					}
				}
				if (flag)
				{
					list.Add(new SharedTarget
					{
						Target = target.DeclaringType?.Name + "." + target.Name,
						Others = (from x in list2.Select(DisplayName)
							orderby x
							select x).ToList(),
						Risky = flag2
					});
				}
			}
			return list;
		}

		private static bool IsRiskyPair(Patches info, string a, string b)
		{
			if (!Has(info.Transpilers, a) && !Has(info.Transpilers, b) && (!Skips(a) || !Has(info.Prefixes, b)) && (!Skips(b) || !Has(info.Prefixes, a)))
			{
				if (Has(info.Finalizers, a))
				{
					return Has(info.Finalizers, b);
				}
				return false;
			}
			return true;
			static bool Has(IEnumerable<Patch> list, string o)
			{
				return list.Any((Patch p) => p.owner == o);
			}
			bool Skips(string o)
			{
				return info.Prefixes.Any((Patch p) => p.owner == o && p.PatchMethod != null && p.PatchMethod.ReturnType == typeof(bool));
			}
		}

		private static MethodBase ResolveTarget(HarmonyMethod m)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Expected I4, but got Unknown
			Type declaringType = m.declaringType;
			if (declaringType == null)
			{
				return null;
			}
			MethodType valueOrDefault = m.methodType.GetValueOrDefault();
			switch (valueOrDefault - 1)
			{
			case 0:
				return AccessTools.DeclaredProperty(declaringType, m.methodName)?.GetGetMethod(nonPublic: true);
			case 1:
				return AccessTools.DeclaredProperty(declaringType, m.methodName)?.GetSetMethod(nonPublic: true);
			case 2:
				return AccessTools.DeclaredConstructor(declaringType, m.argumentTypes, false);
			case 3:
				return declaringType.TypeInitializer;
			default:
				if (m.methodName != null)
				{
					return AccessTools.DeclaredMethod(declaringType, m.methodName, m.argumentTypes, (Type[])null);
				}
				return null;
			}
		}

		private static string Kind(MethodInfo m)
		{
			if (m.IsDefined(typeof(HarmonyPrefix), inherit: false))
			{
				return "prefix";
			}
			if (m.IsDefined(typeof(HarmonyPostfix), inherit: false))
			{
				return "postfix";
			}
			if (m.IsDefined(typeof(HarmonyTranspiler), inherit: false))
			{
				return "transpiler";
			}
			if (m.IsDefined(typeof(HarmonyFinalizer), inherit: false))
			{
				return "finalizer";
			}
			return m.Name switch
			{
				"Prefix" => "prefix", 
				"Postfix" => "postfix", 
				"Transpiler" => "transpiler", 
				"Finalizer" => "finalizer", 
				_ => null, 
			};
		}

		private static string ComputeHash(MethodBase method, out string error)
		{
			error = null;
			try
			{
				string text = DllPathOf(method);
				if (!modules.TryGetValue(text, out var value))
				{
					value = ModuleDefinition.ReadModule((Stream)new MemoryStream(File.ReadAllBytes(text)));
					modules[text] = value;
				}
				IMetadataTokenProvider obj = value.LookupToken(method.MetadataToken);
				MethodDefinition val = (MethodDefinition)(object)((obj is MethodDefinition) ? obj : null);
				if (val == null || !SameMethod(val, method))
				{
					val = FindBySignature(value, method);
				}
				if (val == null)
				{
					error = "dll 中找不到对应方法 / method not found in dll";
					return null;
				}
				return Fingerprint.Hash(val);
			}
			catch (Exception ex)
			{
				error = ex.Message;
				return null;
			}
		}

		private static string DllPathOf(MethodBase method)
		{
			string fullyQualifiedName = method.Module.FullyQualifiedName;
			if (!string.IsNullOrEmpty(fullyQualifiedName) && File.Exists(fullyQualifiedName))
			{
				return fullyQualifiedName;
			}
			string text = (method.DeclaringType ?? method.Module.GetType()).Assembly.GetName().Name + ".dll";
			string text2 = Path.Combine(Paths.ManagedPath, text);
			if (File.Exists(text2))
			{
				return text2;
			}
			throw new FileNotFoundException("找不到原始 dll / original dll not found: " + text);
		}

		private static string CecilTypeName(Type t)
		{
			return t.FullName?.Replace('+', '/');
		}

		private static bool SameMethod(MethodDefinition def, MethodBase method)
		{
			if (((MemberReference)def).Name == method.Name && ((MemberReference)def.DeclaringType).FullName == CecilTypeName(method.DeclaringType))
			{
				return ((MethodReference)def).Parameters.Count == method.GetParameters().Length;
			}
			return false;
		}

		private static MethodDefinition FindBySignature(ModuleDefinition module, MethodBase method)
		{
			TypeDefinition val = ((method.DeclaringType == null) ? null : module.GetType(CecilTypeName(method.DeclaringType)));
			if (val == null)
			{
				return null;
			}
			ParameterInfo[] ps = method.GetParameters();
			List<MethodDefinition> list = ((IEnumerable<MethodDefinition>)val.Methods).Where((MethodDefinition m) => ((MemberReference)m).Name == method.Name && ((MethodReference)m).Parameters.Count == ps.Length && m.IsStatic == method.IsStatic).ToList();
			if (list.Count > 1)
			{
				list = list.Where((MethodDefinition m) => Enumerable.Range(0, ps.Length).All((int i) => ((MemberReference)((ParameterReference)((MethodReference)m).Parameters[i]).ParameterType).FullName.Replace('/', '+').TrimEnd(new char[1] { '&' }) == (ps[i].ParameterType.FullName ?? "").TrimEnd(new char[1] { '&' }))).ToList();
			}
			if (list.Count != 1)
			{
				return null;
			}
			return list[0];
		}

		internal static void ReleaseCache()
		{
			foreach (ModuleDefinition value in modules.Values)
			{
				value.Dispose();
			}
			modules.Clear();
		}
	}
	[AttributeUsage(AttributeTargets.Method)]
	public sealed class GuardAttribute : Attribute
	{
		public string Hash { get; }

		public GuardAttribute(string hash)
		{
			Hash = hash;
		}
	}
	public enum NoticeLevel
	{
		Info,
		Warning,
		Error
	}
	internal sealed class NoticeRecord
	{
		public NoticeLevel Level;

		public string Mod;

		public string SummaryZh;

		public string SummaryEn;

		public string DetailZh;

		public string DetailEn;

		public string Summary => Lang.En(SummaryEn).ZhCN(SummaryZh);

		public string Detail => (string.IsNullOrEmpty(DetailZh) && string.IsNullOrEmpty(DetailEn)) ? null : Lang.En(DetailEn ?? SummaryEn).ZhCN(DetailZh ?? SummaryZh);
	}
	public sealed class PatchFailure
	{
		internal string ReasonZh;

		internal string ReasonEn;

		public string Mod { get; internal set; }

		public string Patch { get; internal set; }

		public string Target { get; internal set; }

		public NoticeLevel Level => NoticeLevel.Error;

		public string Reason => Lang.En(ReasonEn).ZhCN(ReasonZh);
	}
	public sealed class SharedTarget
	{
		public string Mod { get; internal set; }

		public string Target { get; internal set; }

		public List<string> Others { get; internal set; }

		public bool Risky { get; internal set; }
	}
	public sealed class PatchOptions
	{
		public string ModName;

		public Func<PatchFailure, bool> OnFailure;

		public Func<SharedTarget, bool> OnShared;
	}
	internal sealed class ConflictRecord
	{
		public string Mod;

		public string OtherGuid;

		public string OtherName;

		public string Note;
	}
	public sealed class CompatibilityAdvisory
	{
		internal string SummaryZh;

		internal string SummaryEn;

		internal string DetailZh;

		internal string DetailEn;

		internal string DedupeKey;

		public string ReporterMod { get; internal set; }

		public NoticeLevel Level { get; internal set; }

		public string[] RequiredPluginGuids { get; internal set; }

		public string Summary => Lang.En(SummaryEn).ZhCN(SummaryZh);

		public string Detail => (string.IsNullOrEmpty(DetailZh) && string.IsNullOrEmpty(DetailEn)) ? null : Lang.En(DetailEn ?? SummaryEn).ZhCN(DetailZh ?? SummaryZh);
	}
	public enum DialogLevel
	{
		None,
		ErrorOnly,
		WarningAndError,
		All
	}
	[BepInPlugin("com.yummi.dsp.yummiharness", "YummiHarness", "0.2.3")]
	public class HarnessPlugin : BaseUnityPlugin
	{
		private sealed class NoticeItem
		{
			public NoticeLevel Level;

			public string Line;

			public readonly List<string> Details = new List<string>();
		}

		public const string PluginGuid = "com.yummi.dsp.yummiharness";

		public const string PluginVersion = "0.2.3";

		private static readonly Harmony harmony = new Harmony("com.yummi.dsp.yummiharness");

		private static HarnessPlugin instance;

		private ConfigEntry<DialogLevel> dialogLevel;

		private int reportedNotices;

		private bool menuReady;

		private DateTime preloadDoneUtc;

		private int reportedFailures;

		private int lastFlushedChange = -1;

		private readonly HashSet<string> reportedKeys = new HashSet<string>();

		private void Awake()
		{
			instance = this;
			Harness.ModNames["com.yummi.dsp.yummiharness"] = "YummiHarness";
			Harness.LogWarn = delegate(string msg)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)msg);
			};
			Harness.LogInfo = delegate(string msg)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)msg);
			};
			dialogLevel = ((BaseUnityPlugin)this).Config.Bind<DialogLevel>("General", "DialogLevel", DialogLevel.ErrorOnly, "Which notices open the main-menu dialog. None = never; ErrorOnly = vanilla method changed so a patch was skipped; WarningAndError = also conflicts / possible interference; All = also harmless info. Everything is always written to the log. | 哪些提示会在主菜单弹窗。None=不弹;ErrorOnly=官方改了函数导致补丁被跳过;WarningAndError=另含冲突和可能的互相干扰;All=另含无害信息。日志始终全部记录。");
			try
			{
				harmony.PatchAll(typeof(MenuHooks));
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Menu hook failed, using timer fallback: " + ex.Message));
			}
			((MonoBehaviour)this).StartCoroutine(Watch());
		}

		private void OnDestroy()
		{
			harmony.UnpatchSelf();
		}

		internal static void MarkMenuReady()
		{
			if ((Object)(object)instance != (Object)null)
			{
				instance.menuReady = true;
			}
		}

		internal static void StartAfterPreload()
		{
			if ((Object)(object)instance != (Object)null)
			{
				((MonoBehaviour)instance).StartCoroutine(instance.AfterPreload());
			}
		}

		private IEnumerator AfterPreload()
		{
			while (!VFPreload.done)
			{
				yield return null;
			}
			yield return null;
			menuReady = true;
		}

		private IEnumerator Watch()
		{
			while (true)
			{
				yield return (object)new WaitForSeconds(1f);
				if (!menuReady && VFPreload.done)
				{
					if (preloadDoneUtc == default(DateTime))
					{
						preloadDoneUtc = DateTime.UtcNow;
					}
					if ((DateTime.UtcNow - preloadDoneUtc).TotalSeconds > 5.0 && DSPGame.IsMenuDemo)
					{
						menuReady = true;
					}
				}
				bool flag = (DateTime.UtcNow - Harness.LastChangeUtc).TotalSeconds >= 2.0;
				if (menuReady && flag && lastFlushedChange != Harness.ChangeCount)
				{
					lastFlushedChange = Harness.ChangeCount;
					try
					{
						Flush();
					}
					catch (Exception ex)
					{
						((BaseUnityPlugin)this).Logger.LogError((object)("YummiHarness flush failed: " + ex));
					}
					Harness.ReleaseCache();
				}
			}
		}

		private void Flush()
		{
			List<NoticeItem> list = new List<NoticeItem>();
			bool isChinese = Lang.IsChinese;
			List<PatchFailure> source = Harness.Failures.Skip(reportedFailures).ToList();
			reportedFailures = Harness.Failures.Count;
			foreach (IGrouping<string, PatchFailure> item4 in from f in source
				group f by f.Mod)
			{
				int num = item4.Count();
				NoticeItem noticeItem = new NoticeItem
				{
					Level = NoticeLevel.Error,
					Line = (isChinese ? $"{item4.Key}:部分 patch 失败({num} 个),相关功能未启用" : $"{item4.Key}: some patches failed ({num}); related features are disabled")
				};
				foreach (PatchFailure item5 in item4)
				{
					noticeItem.Details.Add("  [" + item5.Mod + "] " + item5.Patch + " -> " + item5.Target + ": " + item5.Reason);
				}
				list.Add(noticeItem);
			}
			foreach (ConflictRecord conflict in Harness.Conflicts)
			{
				string item = "conflict:" + conflict.Mod + "|" + conflict.OtherGuid;
				if (!reportedKeys.Contains(item) && Harness.IsPluginLoaded(conflict.OtherGuid))
				{
					reportedKeys.Add(item);
					NoticeItem noticeItem2 = new NoticeItem
					{
						Level = NoticeLevel.Warning,
						Line = (isChinese ? (conflict.Mod + " 与 " + conflict.OtherName + " 冲突,不能同时使用") : (conflict.Mod + " conflicts with " + conflict.OtherName + "; do not use them together"))
					};
					noticeItem2.Details.Add("  [" + conflict.Mod + "] " + (isChinese ? ("与 " + conflict.OtherName + "(" + conflict.OtherGuid + ")冲突") : ("conflicts with " + conflict.OtherName + " (" + conflict.OtherGuid + ")")) + (string.IsNullOrEmpty(conflict.Note) ? "" : ("  " + conflict.Note)));
					list.Add(noticeItem2);
				}
			}
			foreach (CompatibilityAdvisory advisory in Harness.Advisories)
			{
				string item2 = "advisory:" + advisory.DedupeKey;
				if (reportedKeys.Add(item2) && Harness.AllPluginsLoaded(advisory.RequiredPluginGuids))
				{
					string detail = advisory.Detail;
					NoticeItem noticeItem3 = new NoticeItem
					{
						Level = advisory.Level,
						Line = advisory.Summary
					};
					noticeItem3.Details.Add("  [advisory] [" + advisory.ReporterMod + "] " + advisory.Summary + ((string.IsNullOrEmpty(detail) || detail == advisory.Summary) ? "" : ("  |  " + detail)) + "  (" + string.Join(", ", advisory.RequiredPluginGuids) + ")");
					list.Add(noticeItem3);
				}
			}
			foreach (NoticeRecord item6 in Harness.Notices.Skip(reportedNotices).ToList())
			{
				string detail2 = item6.Detail;
				NoticeItem noticeItem4 = new NoticeItem
				{
					Level = item6.Level,
					Line = item6.Mod + ": " + item6.Summary
				};
				noticeItem4.Details.Add("  [" + item6.Mod + "] " + item6.Summary + ((string.IsNullOrEmpty(detail2) || detail2 == item6.Summary) ? "" : ("  |  " + detail2)));
				list.Add(noticeItem4);
			}
			reportedNotices = Harness.Notices.Count;
			foreach (SharedTarget item7 in Harness.ComputeShared())
			{
				string item3 = "shared:" + item7.Target + "|" + string.Join(",", item7.Others);
				if (reportedKeys.Add(item3))
				{
					string text = string.Join(isChinese ? "、" : ", ", item7.Others);
					NoticeItem noticeItem5 = new NoticeItem();
					noticeItem5.Level = (item7.Risky ? NoticeLevel.Warning : NoticeLevel.Info);
					noticeItem5.Line = (isChinese ? ("多个 mod 修改同一函数 " + item7.Target + (item7.Risky ? "" : "(不会互相干扰)") + ":" + text) : ("Several mods patch " + item7.Target + (item7.Risky ? "" : " (harmless combination)") + ": " + text));
					NoticeItem noticeItem6 = noticeItem5;
					noticeItem6.Details.Add("  [shared] " + item7.Target + ": " + text);
					list.Add(noticeItem6);
				}
			}
			if (list.Count == 0)
			{
				return;
			}
			foreach (NoticeItem item8 in list)
			{
				string text2 = "[" + item8.Level.ToString() + "] " + item8.Line;
				switch (item8.Level)
				{
				case NoticeLevel.Error:
					((BaseUnityPlugin)this).Logger.LogError((object)text2);
					break;
				case NoticeLevel.Warning:
					((BaseUnityPlugin)this).Logger.LogWarning((object)text2);
					break;
				default:
					((BaseUnityPlugin)this).Logger.LogInfo((object)text2);
					break;
				}
				foreach (string detail3 in item8.Details)
				{
					if (item8.Level == NoticeLevel.Error)
					{
						((BaseUnityPlugin)this).Logger.LogError((object)detail3);
					}
					else if (item8.Level == NoticeLevel.Warning)
					{
						((BaseUnityPlugin)this).Logger.LogWarning((object)detail3);
					}
					else
					{
						((BaseUnityPlugin)this).Logger.LogInfo((object)detail3);
					}
				}
			}
			DialogLevel mode = dialogLevel.Value;
			List<NoticeItem> list2 = (from i in list
				where IsShownInDialog(mode, i.Level)
				orderby (int)i.Level descending
				select i).ToList();
			if (list2.Count == 0)
			{
				return;
			}
			NoticeLevel level = list2[0].Level;
			StringBuilder stringBuilder = new StringBuilder(isChinese ? "以下 mod 检测到问题,游戏仍可继续运行。详情请看 BepInEx 日志(LogOutput.log):" : "Some mods reported problems; the game can still run. See the BepInEx log (LogOutput.log) for details:").Append("\n\n");
			int num2 = 0;
			foreach (NoticeItem item9 in list2)
			{
				if (num2++ >= 8)
				{
					stringBuilder.Append("...\n");
					break;
				}
				stringBuilder.Append("- [").Append(item9.Level).Append("] ")
					.Append(item9.Line)
					.Append('\n');
			}
			UIMessageBox.Show(isChinese ? "YummiHarness | 模组提示" : "YummiHarness | Mod notice", stringBuilder.ToString(), Localization.Translate("确定"), (level == NoticeLevel.Error) ? 3 : ((level == NoticeLevel.Warning) ? 1 : 0));
		}

		private static bool IsShownInDialog(DialogLevel mode, NoticeLevel level)
		{
			return mode switch
			{
				DialogLevel.None => false, 
				DialogLevel.ErrorOnly => level == NoticeLevel.Error, 
				DialogLevel.WarningAndError => level >= NoticeLevel.Warning, 
				_ => true, 
			};
		}
	}
	[HarmonyPatch]
	internal static class MenuHooks
	{
		[HarmonyPostfix]
		[HarmonyPatch(typeof(VFPreload), "InvokeOnLoadWorkEnded")]
		[HarmonyPriority(0)]
		public static void InvokeOnLoadWorkEnded_Postfix()
		{
			HarnessPlugin.StartAfterPreload();
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(UIRoot), "_OnOpen")]
		[HarmonyPriority(0)]
		public static void UIRoot_OnOpen_Postfix()
		{
			if (DSPGame.IsMenuDemo)
			{
				HarnessPlugin.StartAfterPreload();
			}
		}
	}
	public static class Lang
	{
		public sealed class Line
		{
			private readonly string en;

			private readonly Dictionary<int, string> text = new Dictionary<int, string>();

			internal Line(string en)
			{
				this.en = en ?? "";
			}

			public Line ZhCN(string value)
			{
				return Set(2052, value);
			}

			public Line FrFR(string value)
			{
				return Set(1036, value);
			}

			public Line DeDE(string value)
			{
				return Set(1031, value);
			}

			public Line EsES(string value)
			{
				return Set(3082, value);
			}

			public Line JaJP(string value)
			{
				return Set(1041, value);
			}

			public Line KoKR(string value)
			{
				return Set(1042, value);
			}

			public Line Set(int lcid, string value)
			{
				if (!string.IsNullOrEmpty(value))
				{
					text[lcid] = value;
				}
				return this;
			}

			public static implicit operator string(Line line)
			{
				if (line != null)
				{
					return line.ToString();
				}
				return "";
			}

			public override string ToString()
			{
				int lcid = Lcid;
				if (text.TryGetValue(lcid, out var value))
				{
					return value;
				}
				int num = lcid & 0x3FF;
				foreach (KeyValuePair<int, string> item in text)
				{
					if ((item.Key & 0x3FF) == num)
					{
						return item.Value;
					}
				}
				return en;
			}
		}

		public const int ZhCN = 2052;

		public const int EnUS = 1033;

		public const int FrFR = 1036;

		public const int DeDE = 1031;

		public const int EsES = 3082;

		public const int JaJP = 1041;

		public const int KoKR = 1042;

		public static int Lcid
		{
			get
			{
				try
				{
					if (Localization.Loaded)
					{
						return Localization.CurrentLanguageLCID;
					}
				}
				catch
				{
				}
				return CultureInfo.CurrentUICulture.LCID;
			}
		}

		public static bool IsChinese => (Lcid & 0x3FF) == 4;

		public static Line En(string en)
		{
			return new Line(en);
		}
	}
	public static class Fingerprint
	{
		public static string Describe(MethodDefinition method)
		{
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d2: Unknown result type (might be due to invalid IL or missing references)
			if (!method.HasBody)
			{
				return ((MemberReference)method).FullName + "\n<no body>";
			}
			MethodBody body = method.Body;
			Dictionary<Instruction, int> dictionary = new Dictionary<Instruction, int>();
			for (int i = 0; i < body.Instructions.Count; i++)
			{
				dictionary[body.Instructions[i]] = i;
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append(".method ").Append(((MemberReference)method).FullName).Append('\n');
			for (int j = 0; j < body.Variables.Count; j++)
			{
				stringBuilder.Append(".local V").Append(j).Append(' ')
					.Append(((MemberReference)((VariableReference)body.Variables[j]).VariableType).FullName)
					.Append('\n');
			}
			Enumerator<ExceptionHandler> enumerator = body.ExceptionHandlers.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					ExceptionHandler current = enumerator.Current;
					stringBuilder.Append(".handler ").Append(current.HandlerType).Append(" try=")
						.Append(Idx(dictionary, current.TryStart))
						.Append('-')
						.Append(Idx(dictionary, current.TryEnd))
						.Append(" handler=")
						.Append(Idx(dictionary, current.HandlerStart))
						.Append('-')
						.Append(Idx(dictionary, current.HandlerEnd));
					if (current.CatchType != null)
					{
						stringBuilder.Append(" catch=").Append(((MemberReference)current.CatchType).FullName);
					}
					stringBuilder.Append('\n');
				}
			}
			finally
			{
				((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
			for (int k = 0; k < body.Instructions.Count; k++)
			{
				Instruction obj = body.Instructions[k];
				OpCode opCode = obj.OpCode;
				string text = ((OpCode)(ref opCode)).Name;
				if (text.EndsWith(".s", StringComparison.Ordinal))
				{
					text = text.Substring(0, text.Length - 2);
				}
				stringBuilder.Append(k.ToString("D4", CultureInfo.InvariantCulture)).Append("  ").Append(text);
				string text2 = Operand(obj.Operand, dictionary);
				if (text2.Length > 0)
				{
					stringBuilder.Append(' ').Append(text2);
				}
				stringBuilder.Append('\n');
			}
			return stringBuilder.ToString();
		}

		public static string Hash(MethodDefinition method)
		{
			return HashText(Describe(method));
		}

		public static string HashText(string text)
		{
			using SHA256 sHA = SHA256.Create();
			byte[] array = sHA.ComputeHash(Encoding.UTF8.GetBytes(text));
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < 8; i++)
			{
				stringBuilder.Append(array[i].ToString("x2", CultureInfo.InvariantCulture));
			}
			return stringBuilder.ToString();
		}

		private static string Idx(Dictionary<Instruction, int> index, Instruction ins)
		{
			if (ins == null)
			{
				return "end";
			}
			if (!index.TryGetValue(ins, out var value))
			{
				return "?";
			}
			return value.ToString(CultureInfo.InvariantCulture);
		}

		private static string Operand(object operand, Dictionary<Instruction, int> index)
		{
			if (operand != null)
			{
				Instruction val = (Instruction)((operand is Instruction) ? operand : null);
				if (val == null)
				{
					if (!(operand is Instruction[] array))
					{
						if (!(operand is string text))
						{
							MemberReference val2 = (MemberReference)((operand is MemberReference) ? operand : null);
							if (val2 == null)
							{
								VariableDefinition val3 = (VariableDefinition)((operand is VariableDefinition) ? operand : null);
								if (val3 == null)
								{
									ParameterDefinition val4 = (ParameterDefinition)((operand is ParameterDefinition) ? operand : null);
									if (val4 == null)
									{
										if (!(operand is float num))
										{
											if (!(operand is double num2))
											{
												if (operand is IFormattable formattable)
												{
													return formattable.ToString(null, CultureInfo.InvariantCulture);
												}
												return operand.ToString();
											}
											return num2.ToString("R", CultureInfo.InvariantCulture);
										}
										return num.ToString("R", CultureInfo.InvariantCulture);
									}
									return "A" + ((ParameterReference)val4).Index.ToString(CultureInfo.InvariantCulture);
								}
								return "V" + ((VariableReference)val3).Index.ToString(CultureInfo.InvariantCulture);
							}
							return val2.FullName;
						}
						return "\"" + text.Replace("\\", "\\\\").Replace("\n", "\\n").Replace("\r", "\\r")
							.Replace("\"", "\\\"") + "\"";
					}
					string[] array2 = new string[array.Length];
					for (int i = 0; i < array.Length; i++)
					{
						array2[i] = Idx(index, array[i]);
					}
					return "-> " + string.Join(",", array2);
				}
				return "-> " + Idx(index, val);
			}
			return "";
		}
	}
}