Decompiled source of RaskatAilments v1.1.0

BepInEx/plugins/RaskatAilments.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;

[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace Raskat.Ailments;

[BepInPlugin("raskat.ailments", "RASKAT Ailments", "1.1.0")]
[BepInDependency("treextr.pathofvalheiman", "0.0.0")]
public sealed class RaskatAilments : BaseUnityPlugin
{
	internal static ManualLogSource Log;

	internal static float ReferenceDamage;

	internal static float PerHitCap;

	internal static float HoldSeconds;

	internal static float DecayPerSecond;

	internal static float FireProcDamageMultiplier;

	internal static float PoisonProcDamageMultiplier;

	private const BindingFlags AllStatic = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

	private Harmony harmony;

	private void Awake()
	{
		//IL_0436: Unknown result type (might be due to invalid IL or missing references)
		//IL_0440: Expected O, but got Unknown
		//IL_0464: Unknown result type (might be due to invalid IL or missing references)
		//IL_0470: Expected O, but got Unknown
		//IL_049e: Unknown result type (might be due to invalid IL or missing references)
		//IL_04aa: Expected O, but got Unknown
		//IL_04d8: Unknown result type (might be due to invalid IL or missing references)
		//IL_04e4: Expected O, but got Unknown
		//IL_0511: Unknown result type (might be due to invalid IL or missing references)
		//IL_051e: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		bool value = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable RASKAT ailment buildup patch. Requires an original PathOfValheiman.dll.").Value;
		ReferenceDamage = ((BaseUnityPlugin)this).Config.Bind<float>("Balance", "ReferenceDamage", 60f, "Buildup = PerHitCap * elementalDamage / (elementalDamage + ReferenceDamage), before the tree bonus.").Value;
		PerHitCap = ((BaseUnityPlugin)this).Config.Bind<float>("Balance", "PerHitCap", 0.4f, "Maximum buildup from a very large hit, before the tree bonus.").Value;
		HoldSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Balance", "HoldSeconds", 6.5f, "Time since the last hit before an ailment gauge starts to decay.").Value;
		DecayPerSecond = ((BaseUnityPlugin)this).Config.Bind<float>("Balance", "DecayPerSecond", 0.175f, "Gauge fraction lost per second after the hold period.").Value;
		FireProcDamageMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Balance", "FireProcDamageMultiplier", 0.6f, "Multiply the accumulated fire damage passed to Path of Valheiman when Ignite triggers; direct hits unchanged.").Value;
		PoisonProcDamageMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Balance", "PoisonProcDamageMultiplier", 0.6f, "Multiply the accumulated poison damage passed to Path of Valheiman when Poison triggers; direct hits unchanged.").Value;
		if (!value)
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Disabled by configuration.");
			return;
		}
		if (!(ReferenceDamage > 0f) || !(ReferenceDamage <= 10000f) || !(PerHitCap > 0f) || !(PerHitCap <= 1f) || !(HoldSeconds >= 0f) || !(HoldSeconds <= 120f) || !(DecayPerSecond > 0f) || !(DecayPerSecond <= 1f) || !(FireProcDamageMultiplier >= 0f) || !(FireProcDamageMultiplier <= 1f) || !(PoisonProcDamageMultiplier >= 0f) || !(PoisonProcDamageMultiplier <= 1f))
		{
			((BaseUnityPlugin)this).Logger.LogError((object)"Invalid balance settings; no methods patched.");
			return;
		}
		try
		{
			Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => a.GetName().Name == "PathOfValheiman");
			if (assembly == null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"PathOfValheiman.dll is not loaded; patch disabled.");
				return;
			}
			Type type = assembly.GetType("PassiveTree.Ailments");
			Type type2 = assembly.GetType("PassiveTree.AilmentGauges");
			if (type == null || type2 == null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Ailment types changed; patch disabled.");
				return;
			}
			MethodInfo method = GetMethod(type, "Grip", typeof(float), 3);
			MethodInfo method2 = GetMethod(type2, "Melt", typeof(void), 2);
			MethodInfo method3 = GetMethod(type2, "Shown", typeof(float), 2);
			MethodInfo method4 = GetMethod(type2, "Feed", typeof(float), 4);
			if (method == null || method2 == null || method3 == null || method4 == null || method.GetParameters()[0].ParameterType != typeof(float) || method.GetParameters()[2].ParameterType != typeof(float) || !method4.GetParameters()[1].ParameterType.IsEnum || method4.GetParameters()[2].ParameterType != typeof(float) || method4.GetParameters()[3].ParameterType != typeof(float))
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Ailment method signatures changed; patch disabled.");
				return;
			}
			if (!Patches.CheckGrip(ILReader.Read(method)) || !Patches.CheckDecay(ILReader.Read(method2), 2) || !Patches.CheckDecay(ILReader.Read(method3), 1) || !Patches.CheckFeed(method4))
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Ailment formula changed (or the main DLL is already modified); patch disabled.");
				return;
			}
			harmony = new Harmony("raskat.ailments");
			Type typeFromHandle = typeof(Patches);
			harmony.Patch((MethodBase)method, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeFromHandle.GetMethod("Grip", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null);
			if (!Patches.GripApplied)
			{
				throw new InvalidOperationException("Grip transpiler refused the method");
			}
			harmony.Patch((MethodBase)method2, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeFromHandle.GetMethod("Melt", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null);
			if (!Patches.MeltApplied)
			{
				throw new InvalidOperationException("Melt transpiler refused the method");
			}
			harmony.Patch((MethodBase)method3, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeFromHandle.GetMethod("Shown", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null);
			if (!Patches.ShownApplied)
			{
				throw new InvalidOperationException("Shown transpiler refused the method");
			}
			harmony.Patch((MethodBase)method4, (HarmonyMethod)null, new HarmonyMethod(typeFromHandle.GetMethod("FeedPostfix", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			((BaseUnityPlugin)this).Logger.LogInfo((object)string.Concat("Active: PathOfValheiman ", assembly.GetName().Version, "; reference=", ReferenceDamage, ", cap=", PerHitCap, ", hold=", HoldSeconds, "s, decay=", DecayPerSecond, "/s, fire proc=", FireProcDamageMultiplier, ", poison proc=", PoisonProcDamageMultiplier, "."));
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)("Ailment patch failed: " + ex));
			if (harmony == null)
			{
				return;
			}
			try
			{
				MethodInfo method5 = typeof(Harmony).GetMethod("UnpatchSelf", Type.EmptyTypes);
				if (method5 != null)
				{
					method5.Invoke(harmony, null);
					return;
				}
				typeof(Harmony).GetMethod("UnpatchAll", new Type[1] { typeof(string) }).Invoke(harmony, new object[1] { "raskat.ailments" });
			}
			catch (Exception ex2)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Could not remove partial patch: " + ex2));
			}
		}
	}

	private static MethodInfo GetMethod(Type type, string name, Type result, int parameterCount)
	{
		MethodInfo[] array = (from m in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
			where m.Name == name && m.ReturnType == result && m.GetParameters().Length == parameterCount
			select m).ToArray();
		return (array.Length != 1) ? null : array[0];
	}
}
internal static class Patches
{
	internal static bool GripApplied;

	internal static bool MeltApplied;

	internal static bool ShownApplied;

	private static bool Float(CodeInstruction c, float value)
	{
		return c.opcode == OpCodes.Ldc_R4 && c.operand is float && (float)c.operand == value;
	}

	private static bool Call(CodeInstruction c)
	{
		return c.opcode == OpCodes.Call || c.opcode == OpCodes.Callvirt;
	}

	private static int GripIndex(IList<CodeInstruction> c)
	{
		int num = -1;
		for (int i = 0; i + 17 < c.Count; i++)
		{
			if (Float(c[i], 1f) && c[i + 1].opcode == OpCodes.Ldarg_1 && Call(c[i + 2]) && Call(c[i + 3]) && c[i + 4].opcode == OpCodes.Stloc_0 && c[i + 5].opcode == OpCodes.Ldarg_0 && c[i + 6].opcode == OpCodes.Ldloc_0 && c[i + 7].opcode == OpCodes.Div && Float(c[i + 8], 0.625f) && c[i + 9].opcode == OpCodes.Div && Float(c[i + 10], 1f) && Float(c[i + 11], 0f) && c[i + 12].opcode == OpCodes.Ldarg_2 && Call(c[i + 13]) && c[i + 14].opcode == OpCodes.Add && c[i + 15].opcode == OpCodes.Mul && Call(c[i + 16]) && c[i + 17].opcode == OpCodes.Ret)
			{
				if (num != -1)
				{
					return -1;
				}
				num = i;
			}
		}
		return num;
	}

	internal static bool CheckGrip(IList<CodeInstruction> c)
	{
		return GripIndex(c) >= 0;
	}

	internal static bool CheckDecay(IList<CodeInstruction> c, int expectedHolds)
	{
		int num = 0;
		int num2 = 0;
		for (int i = 0; i + 1 < c.Count; i++)
		{
			if (Float(c[i], 3f) && c[i + 1].opcode == OpCodes.Sub)
			{
				num++;
			}
			if (Float(c[i], 0.25f) && c[i + 1].opcode == OpCodes.Mul)
			{
				num2++;
			}
		}
		return num == expectedHolds && num2 == 1;
	}

	internal static bool CheckFeed(MethodInfo feed)
	{
		List<CodeInstruction> list = ILReader.Read(feed);
		if (list.Count((CodeInstruction c) => c.opcode == OpCodes.Ret) != 3 || !list.Any((CodeInstruction c) => Float(c, 1f)) || list.Count((CodeInstruction c) => c.opcode == OpCodes.Stelem_R4) < 3)
		{
			return false;
		}
		for (int num = 0; num + 2 < list.Count; num++)
		{
			if (!(list[num].opcode != OpCodes.Ldfld) && !(list[num + 1].opcode != OpCodes.Ldloc_2) && !(list[num + 2].opcode != OpCodes.Ldelem_R4))
			{
				int? num2 = list[num].operand as int?;
				if (num2.HasValue && feed.Module.ResolveField(num2.Value).Name == "Stored")
				{
					return true;
				}
			}
		}
		return false;
	}

	internal static void FeedPostfix(object[] __args, ref float __result)
	{
		if (!(__result <= 0f) && __args != null && __args.Length == 4)
		{
			string text = __args[1].ToString();
			if (text == "Ignite")
			{
				__result *= RaskatAilments.FireProcDamageMultiplier;
			}
			else if (text == "Poison")
			{
				__result *= RaskatAilments.PoisonProcDamageMultiplier;
			}
		}
	}

	internal static IEnumerable<CodeInstruction> Grip(IEnumerable<CodeInstruction> instructions)
	{
		//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c0: Expected O, but got Unknown
		//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d7: Expected O, but got Unknown
		//IL_00df: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e5: Expected O, but got Unknown
		//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f3: Expected O, but got Unknown
		//IL_0104: Unknown result type (might be due to invalid IL or missing references)
		//IL_010a: Expected O, but got Unknown
		//IL_0112: Unknown result type (might be due to invalid IL or missing references)
		//IL_0118: Expected O, but got Unknown
		List<CodeInstruction> list = instructions.Select((Func<CodeInstruction, CodeInstruction>)((CodeInstruction c) => new CodeInstruction(c))).ToList();
		int num = GripIndex(list);
		if (num < 0 || list.Skip(num + 1).Take(9).Any((CodeInstruction c) => c.labels.Count > 0 || c.blocks.Count > 0))
		{
			RaskatAilments.Log.LogError((object)"Grip IL changed; original method preserved.");
			return instructions;
		}
		list[num].opcode = OpCodes.Ldarg_0;
		list[num].operand = null;
		list.RemoveRange(num + 1, 9);
		list.InsertRange(num + 1, (IEnumerable<CodeInstruction>)(object)new CodeInstruction[6]
		{
			new CodeInstruction(OpCodes.Ldarg_0, (object)null),
			new CodeInstruction(OpCodes.Ldc_R4, (object)RaskatAilments.ReferenceDamage),
			new CodeInstruction(OpCodes.Add, (object)null),
			new CodeInstruction(OpCodes.Div, (object)null),
			new CodeInstruction(OpCodes.Ldc_R4, (object)RaskatAilments.PerHitCap),
			new CodeInstruction(OpCodes.Mul, (object)null)
		});
		GripApplied = true;
		return list;
	}

	internal static IEnumerable<CodeInstruction> Melt(IEnumerable<CodeInstruction> instructions)
	{
		return Decay(instructions, 2, "Melt");
	}

	internal static IEnumerable<CodeInstruction> Shown(IEnumerable<CodeInstruction> instructions)
	{
		return Decay(instructions, 1, "Shown");
	}

	private static IEnumerable<CodeInstruction> Decay(IEnumerable<CodeInstruction> instructions, int expectedHolds, string name)
	{
		List<CodeInstruction> list = instructions.Select((Func<CodeInstruction, CodeInstruction>)((CodeInstruction c) => new CodeInstruction(c))).ToList();
		if (!CheckDecay(list, expectedHolds))
		{
			RaskatAilments.Log.LogError((object)(name + " IL changed; original method preserved."));
			return instructions;
		}
		for (int num = 0; num + 1 < list.Count; num++)
		{
			if (Float(list[num], 3f) && list[num + 1].opcode == OpCodes.Sub)
			{
				list[num].operand = RaskatAilments.HoldSeconds;
			}
			else if (Float(list[num], 0.25f) && list[num + 1].opcode == OpCodes.Mul)
			{
				list[num].operand = RaskatAilments.DecayPerSecond;
			}
		}
		if (name == "Melt")
		{
			MeltApplied = true;
		}
		else
		{
			ShownApplied = true;
		}
		return list;
	}
}
internal static class ILReader
{
	private static readonly Dictionary<short, OpCode> Ops = BuildOps();

	private static Dictionary<short, OpCode> BuildOps()
	{
		Dictionary<short, OpCode> dictionary = new Dictionary<short, OpCode>();
		FieldInfo[] fields = typeof(OpCodes).GetFields(BindingFlags.Static | BindingFlags.Public);
		foreach (FieldInfo fieldInfo in fields)
		{
			if (fieldInfo.FieldType == typeof(OpCode))
			{
				OpCode value = (OpCode)fieldInfo.GetValue(null);
				dictionary[value.Value] = value;
			}
		}
		return dictionary;
	}

	internal static List<CodeInstruction> Read(MethodInfo method)
	{
		//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ce: Expected O, but got Unknown
		MethodBody methodBody = method.GetMethodBody();
		if (methodBody == null)
		{
			throw new InvalidOperationException("Method has no IL: " + method.Name);
		}
		byte[] iLAsByteArray = methodBody.GetILAsByteArray();
		List<CodeInstruction> list = new List<CodeInstruction>();
		int num = 0;
		while (num < iLAsByteArray.Length)
		{
			short key = ((iLAsByteArray[num] != 254) ? iLAsByteArray[num] : ((short)(0xFE00 | iLAsByteArray[num + 1])));
			num += ((iLAsByteArray[num] != 254) ? 1 : 2);
			if (!Ops.TryGetValue(key, out var value))
			{
				throw new InvalidOperationException("Unknown IL opcode");
			}
			object obj = null;
			switch (value.OperandType)
			{
			case OperandType.ShortInlineBrTarget:
			case OperandType.ShortInlineI:
			case OperandType.ShortInlineVar:
				obj = iLAsByteArray[num];
				num++;
				break;
			case OperandType.InlineVar:
				obj = BitConverter.ToUInt16(iLAsByteArray, num);
				num += 2;
				break;
			case OperandType.ShortInlineR:
				obj = BitConverter.ToSingle(iLAsByteArray, num);
				num += 4;
				break;
			case OperandType.InlineR:
				obj = BitConverter.ToDouble(iLAsByteArray, num);
				num += 8;
				break;
			case OperandType.InlineBrTarget:
			case OperandType.InlineField:
			case OperandType.InlineI:
			case OperandType.InlineMethod:
			case OperandType.InlineSig:
			case OperandType.InlineString:
			case OperandType.InlineTok:
			case OperandType.InlineType:
				obj = BitConverter.ToInt32(iLAsByteArray, num);
				num += 4;
				break;
			case OperandType.InlineI8:
				obj = BitConverter.ToInt64(iLAsByteArray, num);
				num += 8;
				break;
			case OperandType.InlineSwitch:
			{
				int num2 = BitConverter.ToInt32(iLAsByteArray, num);
				if (num2 < 0 || num2 > 100000)
				{
					throw new InvalidOperationException("Invalid IL switch");
				}
				num += 4 + 4 * num2;
				break;
			}
			default:
				throw new InvalidOperationException("Unsupported IL operand");
			case OperandType.InlineNone:
				break;
			}
			list.Add(new CodeInstruction(value, obj));
		}
		return list;
	}
}