Decompiled source of ValheimCraftingCompatibility v1.0.1

plugins/ValheimCraftingCompatibility.dll

Decompiled 6 hours ago
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using BepInEx;
using BepInEx.Bootstrap;
using HarmonyLib;

[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace ValheimCraftingCompatibility;

[BepInPlugin("local.ValheimCraftingCompatibility", "VikingCrafting / WorkshopWorker Compatibility", "1.0.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public sealed class Plugin : BaseUnityPlugin
{
	private sealed class Registration
	{
		public MethodBase Target;

		public Patch Patch;

		public HarmonyPatchType Kind;
	}

	public const string Id = "local.ValheimCraftingCompatibility";

	public const string VikingId = "JacobsValheim.VikingCraftingAndQuickDeposit";

	public const string WorkerId = "local.WorkshopWorker";

	private const string VikingHash = "ed8941ea6df38410df6171b330c86093a428be062d3f610a39451e0d51d83e8c";

	private const string WorkerHash = "5cc5fe07706e3d0a2064bf048d16dd1204beeafc4b7402ae04a9b585e2fb1a9e";

	private static readonly Dictionary<string, string> Expected = new Dictionary<string, string>
	{
		{ "RecipeQueries.Prefix", "System.Void(System.Boolean&)" },
		{ "RecipeQueries.Finalizer", "System.Exception(System.Exception,System.Boolean)" },
		{ "NearbyCounts.Postfix", "System.Void(Inventory,System.String,System.Int32,System.Boolean,System.Int32&)" },
		{ "NearbyItem.Postfix", "System.Void(Inventory,System.String,System.Int32,System.Boolean,ItemDrop+ItemData&)" },
		{ "NearbyHaveItem.Postfix", "System.Void(Inventory,System.String,System.Boolean,System.Boolean&)" },
		{ "RecipeSpend.Prefix", "System.Void(System.Boolean&)" },
		{ "RecipeSpend.Finalizer", "System.Exception(System.Exception,System.Boolean)" },
		{ "ConsumeNearbyByName.Prefix", "System.Boolean(Inventory,System.String,System.Int32,System.Int32,System.Boolean)" },
		{ "ManualMachineScope.Prefix", "System.Void(UnityEngine.Component,System.Boolean&)" },
		{ "ManualMachineScope.Finalizer", "System.Exception(System.Exception,System.Boolean)" },
		{ "ConsumeNearbyOne.Prefix", "System.Boolean(Inventory,ItemDrop+ItemData,System.Boolean&)" },
		{ "ConsumeNearbyStack.Prefix", "System.Boolean(Inventory,ItemDrop+ItemData,System.Int32,System.Boolean&)" },
		{ "MachineIngredientPreview.Postfix", "System.Void(UnityEngine.Component,System.Reflection.MethodBase,System.String&)" },
		{ "RequirementDisplay.Postfix", "System.Void(UnityEngine.Transform,Piece+Requirement,Player,System.Boolean,System.Int32,System.Int32,System.Boolean)" }
	};

	private void Awake()
	{
		try
		{
			if (!Chainloader.PluginInfos.TryGetValue("JacobsValheim.VikingCraftingAndQuickDeposit", out var value) || !Chainloader.PluginInfos.TryGetValue("local.WorkshopWorker", out var value2))
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"COMPAT STANDBY: both mods are not present; standalone behavior is unchanged.");
				return;
			}
			if (object.ReferenceEquals(value.Instance, null) || object.ReferenceEquals(value2.Instance, null))
			{
				throw new InvalidOperationException("A mod did not initialize. Restore both original author DLLs; do not use the previous V1/V2 replacement DLLs.");
			}
			if (value.Metadata.Version.ToString() != "0.0.5" || value2.Metadata.Version.ToString() != "1.0.5" || Hash(value.Location) != "ed8941ea6df38410df6171b330c86093a428be062d3f610a39451e0d51d83e8c" || Hash(value2.Location) != "5cc5fe07706e3d0a2064bf048d16dd1204beeafc4b7402ae04a9b585e2fb1a9e")
			{
				throw new InvalidOperationException("An author DLL is updated, patched, or unknown. This build accepts only the verified original VikingCrafting 0.0.5 and WorkshopWorker 1.0.5 binaries. Their new builds must be reviewed before compatibility is applied.");
			}
			Apply(((object)value2.Instance).GetType().Assembly);
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)("COMPAT NOT APPLIED: " + ex.Message + " Keep WorkshopWorker [Crafting] Enabled = false, or disable one mod, until this is resolved. See the exception below."));
			((BaseUnityPlugin)this).Logger.LogError((object)ex);
		}
	}

	private static string Hash(string path)
	{
		using FileStream inputStream = File.OpenRead(path);
		using SHA256 sHA = SHA256.Create();
		return BitConverter.ToString(sHA.ComputeHash(inputStream)).Replace("-", "").ToLowerInvariant();
	}

	private static string Signature(MethodInfo method)
	{
		return method.ReturnType.FullName + "(" + string.Join(",", (from p in method.GetParameters()
			select p.ParameterType.FullName).ToArray()) + ")";
	}

	private static string Key(MethodInfo method, Assembly workerAssembly)
	{
		if (method == null || method.DeclaringType == null || method.DeclaringType.Assembly != workerAssembly)
		{
			return null;
		}
		string fullName = method.DeclaringType.FullName;
		if (!fullName.StartsWith("WorkshopWorker.", StringComparison.Ordinal))
		{
			return null;
		}
		return fullName.Substring("WorkshopWorker.".Length) + "." + method.Name;
	}

	private static List<Registration> AllRegistrations()
	{
		List<Registration> list = new List<Registration>();
		MethodBase[] array = Harmony.GetAllPatchedMethods().ToArray();
		foreach (MethodBase methodBase in array)
		{
			Patches patchInfo = Harmony.GetPatchInfo(methodBase);
			if (patchInfo != null)
			{
				Add(list, methodBase, patchInfo.Prefixes, (HarmonyPatchType)1);
				Add(list, methodBase, patchInfo.Postfixes, (HarmonyPatchType)2);
				Add(list, methodBase, patchInfo.Transpilers, (HarmonyPatchType)3);
				Add(list, methodBase, patchInfo.Finalizers, (HarmonyPatchType)4);
			}
		}
		return list;
	}

	private static void Add(List<Registration> list, MethodBase target, IEnumerable<Patch> patches, HarmonyPatchType kind)
	{
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		foreach (Patch patch in patches)
		{
			list.Add(new Registration
			{
				Target = target,
				Patch = patch,
				Kind = kind
			});
		}
	}

	private static string Identity(Registration r)
	{
		//IL_005b: Unknown result type (might be due to invalid IL or missing references)
		return string.Concat(r.Target.Module.ModuleVersionId, ":", r.Target.MetadataToken, ":", r.Patch.owner, ":", r.Kind, ":", r.Patch.PatchMethod.Module.ModuleVersionId, ":", r.Patch.PatchMethod.MetadataToken);
	}

	private void Apply(Assembly workerAssembly)
	{
		//IL_053a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0541: Expected O, but got Unknown
		//IL_059e: Unknown result type (might be due to invalid IL or missing references)
		//IL_05a5: Expected O, but got Unknown
		//IL_064d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0653: Invalid comparison between Unknown and I4
		//IL_066b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0671: Invalid comparison between Unknown and I4
		//IL_0689: Unknown result type (might be due to invalid IL or missing references)
		//IL_068f: Invalid comparison between Unknown and I4
		//IL_0336: Unknown result type (might be due to invalid IL or missing references)
		//IL_033d: Expected O, but got Unknown
		//IL_06a7: Unknown result type (might be due to invalid IL or missing references)
		//IL_06ad: Invalid comparison between Unknown and I4
		Dictionary<string, MethodInfo> dictionary = new Dictionary<string, MethodInfo>();
		foreach (KeyValuePair<string, string> item in Expected)
		{
			int num = item.Key.LastIndexOf('.');
			Type type = workerAssembly.GetType("WorkshopWorker." + item.Key.Substring(0, num), throwOnError: false);
			MethodInfo methodInfo = ((!(type == null)) ? type.GetMethod(item.Key.Substring(num + 1), BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) : null);
			if (methodInfo == null || Signature(methodInfo) != item.Value)
			{
				throw new InvalidOperationException("WorkshopWorker patch signature changed or is missing: " + item.Key);
			}
			dictionary.Add(item.Key, methodInfo);
		}
		Type type2 = workerAssembly.GetType("WorkshopWorker.Crafting", throwOnError: true);
		string[] array = new string[2] { "QueryDepth", "SpendDepth" };
		foreach (string text in array)
		{
			FieldInfo field = type2.GetField(text, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (field == null || field.FieldType != typeof(int) || (int)field.GetValue(null) != 0)
			{
				throw new InvalidOperationException("Crafting scope is unavailable or already active: " + text);
			}
		}
		List<Registration> list = AllRegistrations();
		List<Registration> list2 = list.Where((Registration registration) => registration.Patch.owner == "local.WorkshopWorker" && Key(registration.Patch.PatchMethod, workerAssembly) != null && Expected.ContainsKey(Key(registration.Patch.PatchMethod, workerAssembly))).ToList();
		foreach (MethodInfo method in dictionary.Values)
		{
			if (!list2.Any((Registration registration) => registration.Patch.PatchMethod == method))
			{
				throw new InvalidOperationException("Expected WorkshopWorker hook is not installed: " + method.DeclaringType.Name + "." + method.Name);
			}
		}
		if (!list.Any((Registration registration) => registration.Patch.owner == "JacobsValheim.VikingCraftingAndQuickDeposit"))
		{
			throw new InvalidOperationException("VikingCrafting's Harmony hooks are not installed.");
		}
		string[] first = list.Except(list2).Select(Identity).OrderBy<string, string>((string s) => s, StringComparer.Ordinal)
			.ToArray();
		List<Registration> list3 = (from registration in list2
			group registration by Tuple.Create(registration.Target, registration.Patch.PatchMethod) into g
			select g.First()).ToList();
		List<Registration> list4 = new List<Registration>();
		Harmony val = new Harmony("local.ValheimCraftingCompatibility");
		try
		{
			foreach (Registration item2 in list3)
			{
				list4.Add(item2);
				val.Unpatch(item2.Target, item2.Patch.PatchMethod);
			}
			List<Registration> source = AllRegistrations();
			if (!first.SequenceEqual(source.Select(Identity).OrderBy<string, string>((string s) => s, StringComparer.Ordinal)))
			{
				throw new InvalidOperationException("The resulting hook set does not match the preflight plan.");
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)("COMPAT ACTIVE: removed " + list2.Count + " overlapping WorkshopWorker hook registrations. VikingCrafting handles recipes and manual storage inputs. " + source.Count((Registration registration) => registration.Patch.owner == "local.WorkshopWorker") + " other WorkshopWorker hooks and " + source.Count((Registration registration) => registration.Patch.owner == "JacobsValheim.VikingCraftingAndQuickDeposit") + " VikingCrafting hooks remain installed."));
		}
		catch
		{
			foreach (Registration r in list4.AsEnumerable().Reverse())
			{
				List<Registration> source2 = AllRegistrations();
				foreach (Registration old in list2.Where((Registration s) => s.Target == r.Target && s.Patch.PatchMethod == r.Patch.PatchMethod))
				{
					if (source2.Any((Registration s) => Identity(s) == Identity(old)))
					{
						continue;
					}
					HarmonyMethod val2 = new HarmonyMethod(old.Patch.PatchMethod);
					val2.priority = old.Patch.priority;
					val2.before = old.Patch.before;
					val2.after = old.Patch.after;
					HarmonyMethod val3 = val2;
					Harmony obj2 = new Harmony(old.Patch.owner);
					MethodInfo methodInfo2 = (from m in typeof(Harmony).GetMethods(BindingFlags.Instance | BindingFlags.Public)
						where m.Name == "Patch"
						where m.GetParameters().Length >= 5 && m.GetParameters()[0].ParameterType == typeof(MethodBase) && m.GetParameters().Skip(1).All((ParameterInfo p) => p.ParameterType == typeof(HarmonyMethod))
						orderby m.GetParameters().Length descending
						select m).First();
					object[] array2 = new object[methodInfo2.GetParameters().Length];
					array2[0] = old.Target;
					array2[1] = (((int)old.Kind != 1) ? null : val3);
					array2[2] = (((int)old.Kind != 2) ? null : val3);
					array2[3] = (((int)old.Kind != 3) ? null : val3);
					array2[4] = (((int)old.Kind != 4) ? null : val3);
					methodInfo2.Invoke(obj2, array2);
				}
			}
			throw;
		}
	}
}