Decompiled source of MirageVoiceFrequencyAdjustment v1.0.0

BepInEx/plugins/MirageVoiceFrequencyAdjustment.dll

Decompiled 10 months ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.FSharp.Core;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("Azx-MirageVoiceFrequencyFix")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Azx-MirageVoiceFrequencyFix")]
[assembly: AssemblyCopyright("Copyright ©  2025")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("1c54532b-59c4-46b1-97cf-8535fabb29f4")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace MirageVoiceFrequencyAdjustment;

[BepInPlugin("dev.mirage.voicepatch", "Mirage Standalone Patcher", "1.0.5")]
public sealed class Plugin : BaseUnityPlugin
{
	internal static ManualLogSource Log;

	private Harmony _harmony;

	private static readonly ConcurrentDictionary<object, string> _mvoEnemyMap = new ConcurrentDictionary<object, string>();

	private static readonly Random _rand = new Random();

	private void Awake()
	{
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_001c: Expected O, but got Unknown
		//IL_009f: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ad: Expected O, but got Unknown
		//IL_010b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0117: Expected O, but got Unknown
		//IL_015d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0169: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		_harmony = new Harmony("dev.mirage.voicepatch");
		try
		{
			Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => string.Equals(a.GetName().Name, "Mirage", StringComparison.OrdinalIgnoreCase));
			if (assembly == null)
			{
				Log.LogWarning((object)"Mirage assembly not found. Is qwbarch-Mirage installed? Patch will not run.");
				return;
			}
			MethodInfo methodInfo = FindDelayPickerInvoke(assembly);
			if (methodInfo != null)
			{
				_harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(Plugin).GetMethod("BindEnemyToMvo_Prefix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Log.LogInfo((object)("Patched delay-picker: " + Pretty(methodInfo)));
			}
			else
			{
				Log.LogWarning((object)"Could not locate the delay-picker Invoke to bind mvo->enemy.");
			}
			if (methodInfo != null)
			{
				_harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin).GetMethod("Transpile_RandomNext_Dog10x", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null);
				Log.LogInfo((object)"Transpiled Random.Next for dog 10× delay shrink.");
			}
			MethodInfo methodInfo2 = FindPerTickInvoke(assembly);
			if (methodInfo2 != null)
			{
				_harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin).GetMethod("Transpile_WrapMimicInvokeWithGate", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null);
				Log.LogInfo((object)("Patched per-tick playback invoker: " + Pretty(methodInfo2)));
			}
			else
			{
				Log.LogWarning((object)"Could not locate the per-tick Invoke to gate playback.");
			}
		}
		catch (Exception arg)
		{
			Log.LogError((object)$"MirageStandalonePatcher failed to initialize: {arg}");
		}
	}

	private static string Pretty(MethodBase m)
	{
		return (m == null) ? "<null>" : (m.DeclaringType?.FullName + "::" + m.Name);
	}

	private static MethodInfo FindDelayPickerInvoke(Assembly mirageAsm)
	{
		Type type = mirageAsm.GetTypes().FirstOrDefault((Type t) => t.FullName != null && t.FullName.IndexOf("MimicVoice+clo@61-26", StringComparison.Ordinal) >= 0);
		if (type == null)
		{
			type = mirageAsm.GetTypes().FirstOrDefault((Type t) => t.FullName != null && t.FullName.IndexOf("MimicVoice+", StringComparison.Ordinal) >= 0 && t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Any((FieldInfo f) => f.Name.IndexOf("enemy", StringComparison.OrdinalIgnoreCase) >= 0) && t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Any(IsMvoField));
		}
		return type?.GetMethod("Invoke", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
	}

	private static MethodInfo FindPerTickInvoke(Assembly mirageAsm)
	{
		Type type = mirageAsm.GetTypes().FirstOrDefault((Type t) => t.FullName != null && t.FullName.IndexOf("MimicVoice+clo@69-27", StringComparison.Ordinal) >= 0);
		if (type == null)
		{
			type = mirageAsm.GetTypes().FirstOrDefault((Type t) => t.FullName != null && t.FullName.IndexOf("MimicVoice+", StringComparison.Ordinal) >= 0 && t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Any(IsMvoField) && !t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Any((FieldInfo f) => f.Name.IndexOf("enemy", StringComparison.OrdinalIgnoreCase) >= 0));
		}
		return type?.GetMethod("Invoke", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
	}

	private static bool IsMvoField(FieldInfo f)
	{
		if (!f.FieldType.IsGenericType)
		{
			return false;
		}
		Type genericTypeDefinition = f.FieldType.GetGenericTypeDefinition();
		if (genericTypeDefinition != typeof(FSharpFunc<, >))
		{
			return false;
		}
		Type[] genericArguments = f.FieldType.GetGenericArguments();
		return genericArguments.Length == 2 && genericArguments[0] == typeof(Unit) && genericArguments[1] == typeof(Unit);
	}

	[HarmonyPrefix]
	private static void BindEnemyToMvo_Prefix(object __instance)
	{
		try
		{
			Type type = __instance.GetType();
			BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
			string text = ((type.GetField("enemyAI", bindingAttr) ?? type.GetFields(bindingAttr).FirstOrDefault((FieldInfo f) => f.Name.IndexOf("enemy", StringComparison.OrdinalIgnoreCase) >= 0))?.GetValue(__instance))?.GetType().Name ?? "<null>";
			object obj = (type.GetField("mimicVoice", bindingAttr) ?? type.GetFields(bindingAttr).FirstOrDefault(IsMvoField))?.GetValue(__instance);
			if (obj != null)
			{
				_mvoEnemyMap[obj] = text;
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogDebug((object)$"[MiragePatch.Bind] bound mvo@{RuntimeHelpers.GetHashCode(obj)} to enemy={text}");
				}
			}
		}
		catch (Exception arg)
		{
			ManualLogSource log2 = Log;
			if (log2 != null)
			{
				log2.LogWarning((object)$"BindEnemyToMvo_Prefix failed: {arg}");
			}
		}
	}

	private static IEnumerable<CodeInstruction> Transpile_RandomNext_Dog10x(IEnumerable<CodeInstruction> instructions, ILGenerator il)
	{
		//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ab: Expected O, but got Unknown
		List<CodeInstruction> list = new List<CodeInstruction>(instructions);
		MethodInfo method = typeof(Random).GetMethod("Next", new Type[2]
		{
			typeof(int),
			typeof(int)
		});
		MethodInfo method2 = typeof(Plugin).GetMethod("RandomNextPatched", BindingFlags.Static | BindingFlags.NonPublic);
		for (int i = 0; i < list.Count; i++)
		{
			CodeInstruction val = list[i];
			if (val.opcode == OpCodes.Callvirt && val.operand is MethodInfo methodInfo && methodInfo == method)
			{
				list.Insert(i, new CodeInstruction(OpCodes.Ldarg_0, (object)null));
				i++;
				val.opcode = OpCodes.Call;
				val.operand = method2;
			}
		}
		return list;
	}

	private static int RandomNextPatched(Random rng, int min, int max, object delayPickerClosure)
	{
		try
		{
			BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
			string text = (delayPickerClosure?.GetType().GetField("enemyAI", bindingAttr) ?? delayPickerClosure?.GetType().GetFields(bindingAttr).FirstOrDefault((FieldInfo f) => f.Name.IndexOf("enemy", StringComparison.OrdinalIgnoreCase) >= 0))?.GetValue(delayPickerClosure)?.GetType().Name;
			if (text == "MouthDogAI")
			{
				int num = Math.Max(1, min / 10);
				int maxValue = Math.Max(num + 1, (max + 9) / 10);
				return rng.Next(num, maxValue);
			}
			return rng.Next(min, max);
		}
		catch (Exception arg)
		{
			ManualLogSource log = Log;
			if (log != null)
			{
				log.LogWarning((object)$"RandomNextPatched fell back due to: {arg}");
			}
			return rng.Next(min, max);
		}
	}

	private static IEnumerable<CodeInstruction> Transpile_WrapMimicInvokeWithGate(IEnumerable<CodeInstruction> instructions, ILGenerator il)
	{
		//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c5: Expected O, but got Unknown
		//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e1: Expected O, but got Unknown
		//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f3: Expected O, but got Unknown
		//IL_0105: Unknown result type (might be due to invalid IL or missing references)
		//IL_010f: Expected O, but got Unknown
		//IL_0122: Unknown result type (might be due to invalid IL or missing references)
		//IL_012c: Expected O, but got Unknown
		//IL_0134: Unknown result type (might be due to invalid IL or missing references)
		//IL_013e: Expected O, but got Unknown
		List<CodeInstruction> list = new List<CodeInstruction>(instructions);
		MethodInfo method = typeof(FSharpFunc<Unit, Unit>).GetMethod("Invoke");
		MethodInfo method2 = typeof(Plugin).GetMethod("InvokeMimicWithSkip", BindingFlags.Static | BindingFlags.NonPublic);
		LocalBuilder localBuilder = il.DeclareLocal(typeof(FSharpFunc<Unit, Unit>));
		LocalBuilder localBuilder2 = il.DeclareLocal(typeof(Unit));
		for (int i = 0; i < list.Count; i++)
		{
			CodeInstruction val = list[i];
			if (val.opcode == OpCodes.Callvirt && val.operand is MethodInfo methodInfo && methodInfo == method)
			{
				List<CodeInstruction> list2 = new List<CodeInstruction>
				{
					new CodeInstruction(OpCodes.Stloc, (object)localBuilder2.LocalIndex),
					new CodeInstruction(OpCodes.Stloc, (object)localBuilder.LocalIndex),
					new CodeInstruction(OpCodes.Ldarg_0, (object)null),
					new CodeInstruction(OpCodes.Ldloc, (object)localBuilder.LocalIndex),
					new CodeInstruction(OpCodes.Ldloc, (object)localBuilder2.LocalIndex),
					new CodeInstruction(OpCodes.Call, (object)method2)
				};
				list.RemoveAt(i);
				list.InsertRange(i, list2);
				i += list2.Count - 1;
			}
		}
		return list;
	}

	private static Unit InvokeMimicWithSkip(object perTickClosure, FSharpFunc<Unit, Unit> mimicVoice, Unit unitArg)
	{
		try
		{
			_mvoEnemyMap.TryGetValue(mimicVoice, out var value);
			if (value == "MaskedPlayerEnemy" || value == "MouthDogAI")
			{
				return mimicVoice.Invoke(unitArg);
			}
			int num = _rand.Next(1, 101);
			if (num != 1)
			{
				return unitArg;
			}
			return mimicVoice.Invoke(unitArg);
		}
		catch (Exception arg)
		{
			ManualLogSource log = Log;
			if (log != null)
			{
				log.LogWarning((object)$"InvokeMimicWithSkip encountered an issue: {arg}");
			}
			return mimicVoice.Invoke(unitArg);
		}
	}
}