Decompiled source of ReliableBlockRebuilt v1.1.0

plugins/ReliableBlockRebuilt/ReliableBlock.Rebuilt.dll

Decompiled a month ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
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: AssemblyCompany("Korppis;anneryeo")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Local-player Reliable Block update with strict compatibility guards.")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0+4250ba3708815e401280d7e3feed5b71c9c0d5fc")]
[assembly: AssemblyProduct("ReliableBlock.Rebuilt")]
[assembly: AssemblyTitle("ReliableBlock.Rebuilt")]
[assembly: AssemblyVersion("1.1.0.0")]
[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 ReliableBlock
{
	[BepInPlugin("org.bepinex.plugins.reliableblock.rebuilt", "Reliable Block Rebuilt", "1.1.0")]
	[BepInProcess("valheim.exe")]
	[BepInIncompatibility("org.bepinex.plugins.reliableblock")]
	public sealed class ReliableBlockPlugin : BaseUnityPlugin
	{
		public const string PluginId = "org.bepinex.plugins.reliableblock.rebuilt";

		private Harmony harmony;

		private ConfigEntry<bool> modEnabled;

		private MethodInfo target;

		private bool conflictReported;

		internal static ReliableBlockPlugin Instance;

		private void Awake()
		{
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Expected O, but got Unknown
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Expected O, but got Unknown
			Instance = this;
			modEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable local-player damage mitigation on staggered blocks. False uses the original block check. Restart after changing mods.");
			try
			{
				target = AccessTools.DeclaredMethod(typeof(Humanoid), "BlockAttack", new Type[2]
				{
					typeof(HitData),
					typeof(Character)
				}, (Type[])null);
				if (target == null || !Compatibility.IsSupported(target.GetMethodBody()?.GetILAsByteArray()))
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"Disabled: unrecognized BlockAttack code. Inspected against Valheim 1.0.16. No patch applied.");
				}
				else if (!HasOtherPatches())
				{
					harmony = new Harmony("org.bepinex.plugins.reliableblock.rebuilt");
					harmony.Patch((MethodBase)target, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(ReliableBlockPlugin), "Transpiler", (Type[])null)
					{
						priority = 0
					}, (HarmonyMethod)null, (HarmonyMethod)null);
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Disabled: patch installation failed. " + ex));
				Harmony obj = harmony;
				if (obj != null)
				{
					obj.UnpatchSelf();
				}
			}
		}

		private bool HasOtherPatches()
		{
			Patches patchInfo = Harmony.GetPatchInfo((MethodBase)target);
			string[] array = ((patchInfo != null) ? patchInfo.Owners.Where((string owner) => owner != "org.bepinex.plugins.reliableblock.rebuilt").ToArray() : null);
			if (array == null || array.Length == 0)
			{
				return false;
			}
			if (!conflictReported)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Disabled: other BlockAttack patches detected: " + string.Join(", ", array) + ". Remove the conflict and restart."));
				conflictReported = true;
			}
			return true;
		}

		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			List<CodeInstruction> list = instructions.ToList();
			try
			{
				if (Instance.HasOtherPatches())
				{
					return list;
				}
				if (!BlockPatch.TryRewrite(list, out var output, out var reason))
				{
					((BaseUnityPlugin)Instance).Logger.LogWarning((object)("Disabled: " + reason + ". Incoming method instructions left unchanged."));
					return list;
				}
				((BaseUnityPlugin)Instance).Logger.LogInfo((object)"Verified BlockAttack patch installed for Valheim 1.0.16; local player only. Live gameplay/crossplay validation is still required.");
				return output;
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)Instance).Logger.LogError((object)("Disabled: patch validation failed; incoming instructions left unchanged. " + ex));
				return list;
			}
		}

		internal static bool ShouldRejectForStagger(bool staggered, Humanoid defender)
		{
			if (!staggered)
			{
				return false;
			}
			try
			{
				ReliableBlockPlugin instance = Instance;
				return (Object)(object)instance == (Object)null || !instance.modEnabled.Value || !(defender is Player) || (object)defender != Player.m_localPlayer || !((Character)defender).IsOwner() || ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsDedicated()) || instance.HasOtherPatches();
			}
			catch
			{
				return true;
			}
		}

		private void OnDestroy()
		{
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			if (Instance == this)
			{
				Instance = null;
			}
		}
	}
	internal static class BlockPatch
	{
		internal static bool TryRewrite(IReadOnlyList<CodeInstruction> input, out List<CodeInstruction> output, out string reason)
		{
			//IL_052e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0534: Expected O, but got Unknown
			//IL_053d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0543: Expected O, but got Unknown
			output = input.ToList();
			reason = "expected unique stagger/stamina/mitigation sequence was not found";
			int[] array = (from i in Enumerable.Range(0, input.Count)
				where Calls(input[i], typeof(Character), "AddStaggerDamage")
				select i).ToArray();
			int[] array2 = (from i in Enumerable.Range(0, input.Count)
				where Calls(input[i], typeof(HitData), "BlockDamage")
				select i).ToArray();
			if (array.Length != 1 || array2.Length != 1)
			{
				return false;
			}
			int num = array[0];
			if (num + 21 >= input.Count || array2[0] != num + 21)
			{
				return false;
			}
			if (Local(input[num + 1], load: false) < 0 || input[num + 2].opcode != OpCodes.Ldarg_0 || input[num + 3].opcode != OpCodes.Ldc_R4 || !object.Equals(input[num + 3].operand, 0f) || !Calls(input[num + 4], typeof(Character), "HaveStamina") || input[num + 5].opcode != OpCodes.Dup || !Branch(input[num + 6], whenTrue: false) || Local(input[num + 7], load: true) != Local(input[num + 1], load: false) || input[num + 8].opcode != OpCodes.Ldc_I4_0 || input[num + 9].opcode != OpCodes.Ceq || (!(input[num + 10].opcode == OpCodes.Br) && !(input[num + 10].opcode == OpCodes.Br_S)) || input[num + 11].opcode != OpCodes.Ldc_I4_0 || Local(input[num + 12], load: false) < 0 || Local(input[num + 12], load: false) == Local(input[num + 1], load: false) || !Branch(input[num + 13], whenTrue: false) || Local(input[num + 14], load: true) != Local(input[num + 1], load: false) || !Branch(input[num + 15], whenTrue: true) || Destination(input[num + 13], input) != Destination(input[num + 15], input) || !Targets(input[num + 6], input[num + 11]) || !Targets(input[num + 10], input[num + 12]) || input[num + 16].opcode != OpCodes.Ldarg_1 || input[num + 17].opcode != OpCodes.Ldc_I4_0 || input[num + 18].opcode != OpCodes.Stfld || !(input[num + 18].operand is FieldInfo fieldInfo) || fieldInfo.DeclaringType != typeof(HitData) || fieldInfo.Name != "m_statusEffectHash" || input[num + 19].opcode != OpCodes.Ldarg_1 || Local(input[num + 20], load: true) < 0)
			{
				return false;
			}
			if (!TargetsAfter(input[num + 15], input, num + 21))
			{
				return false;
			}
			if (input.Skip(num).Take(22).Any((CodeInstruction i) => i.blocks.Count != 0) || input[num + 15].labels.Count != 0)
			{
				return false;
			}
			MethodInfo method = typeof(ReliableBlockPlugin).GetMethod("ShouldRejectForStagger", BindingFlags.Static | BindingFlags.NonPublic);
			if (method == null)
			{
				return false;
			}
			output.InsertRange(num + 15, (IEnumerable<CodeInstruction>)(object)new CodeInstruction[2]
			{
				new CodeInstruction(OpCodes.Ldarg_0, (object)null),
				new CodeInstruction(OpCodes.Call, (object)method)
			});
			reason = "verified";
			return true;
		}

		private static bool Calls(CodeInstruction code, Type owner, string name)
		{
			if ((code.opcode == OpCodes.Call || code.opcode == OpCodes.Callvirt) && code.operand is MethodInfo methodInfo && methodInfo.DeclaringType == owner)
			{
				return methodInfo.Name == name;
			}
			return false;
		}

		private static bool Branch(CodeInstruction code, bool whenTrue)
		{
			if (!whenTrue)
			{
				if (!(code.opcode == OpCodes.Brfalse))
				{
					return code.opcode == OpCodes.Brfalse_S;
				}
				return true;
			}
			if (!(code.opcode == OpCodes.Brtrue))
			{
				return code.opcode == OpCodes.Brtrue_S;
			}
			return true;
		}

		private static bool Targets(CodeInstruction branch, CodeInstruction destination)
		{
			if (branch.operand is Label item)
			{
				return destination.labels.Contains(item);
			}
			return false;
		}

		private static bool TargetsAfter(CodeInstruction branch, IReadOnlyList<CodeInstruction> codes, int after)
		{
			return Destination(branch, codes) > after;
		}

		private static int Destination(CodeInstruction branch, IReadOnlyList<CodeInstruction> codes)
		{
			object operand = branch.operand;
			if (operand is Label)
			{
				Label label = (Label)operand;
				int[] array = (from i in Enumerable.Range(0, codes.Count)
					where codes[i].labels.Contains(label)
					select i).ToArray();
				if (array.Length != 1)
				{
					return -1;
				}
				return array[0];
			}
			return -1;
		}

		private static int Local(CodeInstruction code, bool load)
		{
			OpCode opcode = code.opcode;
			if (opcode == (load ? OpCodes.Ldloc_0 : OpCodes.Stloc_0))
			{
				return 0;
			}
			if (opcode == (load ? OpCodes.Ldloc_1 : OpCodes.Stloc_1))
			{
				return 1;
			}
			if (opcode == (load ? OpCodes.Ldloc_2 : OpCodes.Stloc_2))
			{
				return 2;
			}
			if (opcode == (load ? OpCodes.Ldloc_3 : OpCodes.Stloc_3))
			{
				return 3;
			}
			if (opcode != (load ? OpCodes.Ldloc : OpCodes.Stloc) && opcode != (load ? OpCodes.Ldloc_S : OpCodes.Stloc_S))
			{
				return -1;
			}
			if (!(code.operand is LocalBuilder localBuilder))
			{
				object operand = code.operand;
				if (operand is int)
				{
					return (int)operand;
				}
				operand = code.operand;
				if (operand is byte)
				{
					return (byte)operand;
				}
				operand = code.operand;
				if (operand is short)
				{
					return (short)operand;
				}
				return -1;
			}
			return localBuilder.LocalIndex;
		}
	}
	internal static class Compatibility
	{
		internal const string SupportedBlockAttackSha256 = "11166b4c192cb90da4d05a27153b89972408bea4a4c4cf92d178e0db698ca80c";

		internal static bool IsSupported(byte[] bytes)
		{
			if (bytes == null || bytes.Length == 0)
			{
				return false;
			}
			using SHA256 sHA = SHA256.Create();
			return BitConverter.ToString(sHA.ComputeHash(bytes)).Replace("-", "").ToLowerInvariant() == "11166b4c192cb90da4d05a27153b89972408bea4a4c4cf92d178e0db698ca80c";
		}
	}
}