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 "";
}
}
}