Decompiled source of EpicLoot RunicCrafting Bridge v1.0.3

BepInEx/plugins/EpicLoot_RunicCrafting_Bridge/EpicLootRunicCraftingBridge.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using EpicLoot;
using Microsoft.CodeAnalysis;
using RunicCrafting.Domain;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8.1", FrameworkDisplayName = ".NET Framework 4.8.1")]
[assembly: AssemblyCompany("EpicLootRunicCraftingBridge")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+68764a1b42da50d6641aad0b6bdaa9480d805657")]
[assembly: AssemblyProduct("EpicLootRunicCraftingBridge")]
[assembly: AssemblyTitle("EpicLootRunicCraftingBridge")]
[assembly: AssemblyVersion("1.0.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 EpicLootRunicCraftingBridge
{
	[BepInPlugin("chazman.EpicLootRunicCraftingBridge", "EpicLoot Runic Crafting Bridge", "1.0.3")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInProcess("valheim.exe")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "chazman.EpicLootRunicCraftingBridge";

		public const string PluginName = "EpicLoot Runic Crafting Bridge";

		public const string PluginVersion = "1.0.3";

		private const string EpicLootGuid = "randyknapp.mods.epicloot";

		private const string RunicCraftingGuid = "chazman.RunicCrafting";

		private const string ProviderId = "chazman.EpicLootRunicCraftingBridge.nearby-containers";

		private readonly object _providerGate = new object();

		private RunicCraftingAdapter _runicCrafting;

		private bool _registered;

		private string _lastFailure;

		private void Awake()
		{
			try
			{
				_runicCrafting = new RunicCraftingAdapter(delegate(string message)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)message);
				});
				_registered = API.RegisterInventoryProvider("chazman.EpicLootRunicCraftingBridge.nearby-containers", (Func<List<ItemData>>)GetSelectableItems, (Func<string, int>)CountItem, (Func<string, int, int>)RemoveItem, (Func<ItemData, int, int>)null);
				if (!_registered)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)"EpicLoot rejected the nearby-container inventory provider; the bridge is inactive.");
				}
				else
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)"Registered Runic Crafting nearby containers as an EpicLoot enchanting material provider.");
				}
			}
			catch (Exception arg)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)$"Bridge startup failed and no provider was registered: {arg}");
			}
		}

		private void OnDestroy()
		{
			if (_registered)
			{
				API.UnregisterInventoryProvider("chazman.EpicLootRunicCraftingBridge.nearby-containers");
				_registered = false;
			}
		}

		private static List<ItemData> GetSelectableItems()
		{
			return new List<ItemData>();
		}

		private int CountItem(string resourceId)
		{
			if (string.IsNullOrEmpty(resourceId))
			{
				return 0;
			}
			lock (_providerGate)
			{
				try
				{
					int result = _runicCrafting.CountNearby(resourceId);
					ClearFailure();
					return result;
				}
				catch (Exception exception)
				{
					ReportFailure("count", exception);
					return 0;
				}
			}
		}

		private int RemoveItem(string resourceId, int amount)
		{
			if (string.IsNullOrEmpty(resourceId) || amount <= 0)
			{
				return 0;
			}
			lock (_providerGate)
			{
				try
				{
					int result = _runicCrafting.RemoveNearby(resourceId, amount);
					ClearFailure();
					return result;
				}
				catch (Exception exception)
				{
					ReportFailure("remove", exception);
					return 0;
				}
			}
		}

		private void ReportFailure(string operation, Exception exception)
		{
			Exception ex = ((exception is TargetInvocationException { InnerException: not null } ex2) ? ex2.InnerException : exception);
			string text = operation + ":" + ex.GetType().Name + ":" + ex.Message;
			if (!(text == _lastFailure))
			{
				_lastFailure = text;
				((BaseUnityPlugin)this).Logger.LogError((object)$"Nearby-container {operation} failed; EpicLoot will use character inventory only. {ex}");
			}
		}

		private void ClearFailure()
		{
			_lastFailure = null;
		}
	}
	internal sealed class RunicCraftingAdapter
	{
		private const BindingFlags StaticMembers = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		private const BindingFlags InstanceMembers = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

		private readonly object _containerQuery;

		private readonly MethodInfo _resolveSources;

		private readonly PropertyInfo _enabled;

		private readonly PropertyInfo _craftFromContainers;

		private readonly PropertyInfo _safeInteractionRange;

		private readonly MethodInfo _resourceIdForPrefab;

		private readonly Action<string> _log;

		private readonly Dictionary<string, string> _resourceIdCache = new Dictionary<string, string>(StringComparer.Ordinal);

		private readonly Dictionary<string, string> _lastReportedCountStates = new Dictionary<string, string>(StringComparer.Ordinal);

		private readonly ExactMaterialTransactionEngine _transactionEngine = new ExactMaterialTransactionEngine(new ExactMaterialPlanner());

		internal RunicCraftingAdapter(Action<string> log)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Expected O, but got Unknown
			_log = log;
			Assembly assembly = typeof(MaterialRequirement).Assembly;
			Version version = new Version(1, 1, 0, 0);
			if (!version.Equals(assembly.GetName().Version))
			{
				throw new NotSupportedException($"Runic Crafting {assembly.GetName().Version} is not the validated {version} build.");
			}
			Type type = RequireType(assembly, "RunicCrafting.Configuration");
			Type type2 = RequireType(assembly, "RunicCrafting.Integration.WorkshopAccessRuntime");
			Type type3 = RequireType(assembly, "RunicCrafting.Integration.ContainerQueryRuntime");
			Type type4 = RequireType(assembly, "RunicCrafting.Integration.ValheimReflection");
			_enabled = RequireProperty(type, "Enabled");
			_craftFromContainers = RequireProperty(type, "CraftFromContainers");
			_safeInteractionRange = RequireProperty(type, "SafeInteractionRange");
			_resourceIdForPrefab = type4.GetMethod("ResourceId", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(ItemDrop) }, null);
			if (_resourceIdForPrefab == null || _resourceIdForPrefab.ReturnType != typeof(string))
			{
				throw new MissingMethodException(type4.FullName, "ResourceId(ItemDrop)");
			}
			object obj = Activator.CreateInstance(type2, nonPublic: true);
			_containerQuery = Activator.CreateInstance(type3, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new object[1] { obj }, null);
			Type[] types = new Type[10]
			{
				typeof(Player),
				typeof(CraftingStation),
				typeof(Vector3),
				typeof(float),
				typeof(IEnumerable<MaterialRequirement>),
				typeof(string),
				typeof(bool),
				typeof(string).MakeByRefType(),
				typeof(bool),
				typeof(bool)
			};
			_resolveSources = type3.GetMethod("ResolveSources", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, types, null);
			if (_containerQuery == null || _resolveSources == null || !typeof(IReadOnlyList<IMutableMaterialSource>).IsAssignableFrom(_resolveSources.ReturnType))
			{
				throw new MissingMethodException(type3.FullName, "ResolveSources");
			}
		}

		internal int CountNearby(string resourceId)
		{
			Player localPlayer = Player.m_localPlayer;
			if (!Available(localPlayer))
			{
				return 0;
			}
			resourceId = ResolveResourceId(resourceId);
			string reason;
			IReadOnlyList<IMutableMaterialSource> readOnlyList = NearbyOnly(Resolve(localPlayer, resourceId, 1, requireWritable: false, out reason));
			long num = 0L;
			foreach (IMutableMaterialSource item in readOnlyList)
			{
				num += Math.Max(0, item.Snapshot().Available(resourceId));
				if (num >= int.MaxValue)
				{
					ReportCount(resourceId, int.MaxValue, readOnlyList.Count, reason);
					return int.MaxValue;
				}
			}
			int num2 = (int)num;
			ReportCount(resourceId, num2, readOnlyList.Count, reason);
			return num2;
		}

		internal int RemoveNearby(string resourceId, int amount)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Expected O, but got Unknown
			Player localPlayer = Player.m_localPlayer;
			if (!Available(localPlayer))
			{
				return 0;
			}
			resourceId = ResolveResourceId(resourceId);
			MaterialRequirement[] array = (MaterialRequirement[])(object)new MaterialRequirement[1]
			{
				new MaterialRequirement(resourceId, amount)
			};
			string reason;
			IReadOnlyList<IMutableMaterialSource> readOnlyList = NearbyOnly(Resolve(localPlayer, resourceId, amount, requireWritable: true, out reason));
			MaterialConsumptionLease val = default(MaterialConsumptionLease);
			string arg = default(string);
			if (!_transactionEngine.TryBegin((IEnumerable<MaterialRequirement>)array, (IEnumerable<IMutableMaterialSource>)readOnlyList, ref val, ref arg))
			{
				_log?.Invoke($"Runic Crafting could not reserve {amount} {resourceId}; " + $"sources={readOnlyList.Count}; query={reason}; transaction={arg}.");
				return 0;
			}
			try
			{
				val.Commit();
				return amount;
			}
			finally
			{
				val.Dispose();
			}
		}

		private string ResolveResourceId(string itemName)
		{
			if (_resourceIdCache.TryGetValue(itemName, out var value))
			{
				return value;
			}
			ObjectDB instance = ObjectDB.instance;
			if ((Object)(object)instance == (Object)null || instance.m_items == null)
			{
				return itemName;
			}
			foreach (GameObject item in instance.m_items)
			{
				ItemDrop val = (((Object)(object)item == (Object)null) ? null : item.GetComponent<ItemDrop>());
				ItemData val2 = val?.m_itemData;
				if (val2?.m_shared != null && string.Equals(val2.m_shared.m_name, itemName, StringComparison.Ordinal))
				{
					string text = _resourceIdForPrefab.Invoke(null, new object[1] { val }) as string;
					if (!string.IsNullOrEmpty(text))
					{
						return CacheMapping(itemName, text);
					}
				}
			}
			if (itemName.StartsWith("$item_", StringComparison.Ordinal))
			{
				string b = itemName.Substring("$item_".Length);
				foreach (GameObject item2 in instance.m_items)
				{
					ItemDrop val3 = (((Object)(object)item2 == (Object)null) ? null : item2.GetComponent<ItemDrop>());
					if (val3?.m_itemData != null)
					{
						string text2 = _resourceIdForPrefab.Invoke(null, new object[1] { val3 }) as string;
						if (string.Equals(text2, b, StringComparison.OrdinalIgnoreCase))
						{
							return CacheMapping(itemName, text2);
						}
					}
				}
			}
			return itemName;
		}

		private string CacheMapping(string itemName, string resourceId)
		{
			_resourceIdCache[itemName] = resourceId;
			if (!string.Equals(itemName, resourceId, StringComparison.Ordinal))
			{
				_log?.Invoke("Mapped EpicLoot material " + itemName + " to Runic Crafting resource " + resourceId + ".");
			}
			return resourceId;
		}

		private IReadOnlyList<IMutableMaterialSource> Resolve(Player player, string resourceId, int amount, bool requireWritable, out string reason)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Expected O, but got Unknown
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			MaterialRequirement[] array = (MaterialRequirement[])(object)new MaterialRequirement[1]
			{
				new MaterialRequirement(resourceId, amount)
			};
			object[] array2 = new object[10]
			{
				player,
				null,
				((Component)player).transform.position,
				GetStaticValue<float>(_safeInteractionRange),
				array,
				"epicloot.enchanting",
				true,
				null,
				requireWritable,
				true
			};
			object? obj = _resolveSources.Invoke(_containerQuery, array2);
			reason = (array2[7] as string) ?? string.Empty;
			if (obj is IReadOnlyList<IMutableMaterialSource> result)
			{
				return result;
			}
			throw new InvalidOperationException("Runic Crafting returned an incompatible material-source list.");
		}

		private static IReadOnlyList<IMutableMaterialSource> NearbyOnly(IReadOnlyList<IMutableMaterialSource> sources)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Invalid comparison between Unknown and I4
			List<IMutableMaterialSource> list = new List<IMutableMaterialSource>();
			foreach (IMutableMaterialSource source in sources)
			{
				if (source != null && (int)source.Snapshot().Kind == 1)
				{
					list.Add(source);
				}
			}
			return list.AsReadOnly();
		}

		private void ReportCount(string resourceId, int count, int sourceCount, string reason)
		{
			string text = $"{count}|{sourceCount}|{reason}";
			if (!_lastReportedCountStates.TryGetValue(resourceId, out var value) || !string.Equals(value, text, StringComparison.Ordinal))
			{
				_lastReportedCountStates[resourceId] = text;
				_log?.Invoke($"Runic Crafting nearby count {resourceId}={count}; " + $"sources={sourceCount}; query={reason}.");
			}
		}

		private bool Available(Player player)
		{
			if ((Object)(object)player != (Object)null && GetConfigValue<bool>(_enabled))
			{
				return GetConfigValue<bool>(_craftFromContainers);
			}
			return false;
		}

		private static T GetConfigValue<T>(PropertyInfo property)
		{
			return ((property.GetValue(null, null) as ConfigEntry<T>) ?? throw new InvalidOperationException("Runic Crafting setting " + property.Name + " has an incompatible type.")).Value;
		}

		private static T GetStaticValue<T>(PropertyInfo property)
		{
			object value = property.GetValue(null, null);
			if (value is T)
			{
				return (T)value;
			}
			throw new InvalidOperationException("Runic Crafting value " + property.Name + " has an incompatible type.");
		}

		private static Type RequireType(Assembly assembly, string fullName)
		{
			return assembly.GetType(fullName, throwOnError: false) ?? throw new TypeLoadException("Required Runic Crafting type not found: " + fullName);
		}

		private static PropertyInfo RequireProperty(Type type, string name)
		{
			return type.GetProperty(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?? throw new MissingMemberException(type.FullName, name);
		}
	}
}