Decompiled source of Soulcatcher Crossbow Fix v1.0.0

SoulcatcherCrossbowFix.dll

Decompiled 5 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("SoulcatcherCrossbowFix")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Fixes Crossbow interactions with Soulcatcher mod")]
[assembly: AssemblyFileVersion("1.0.3.0")]
[assembly: AssemblyInformationalVersion("1.0.3")]
[assembly: AssemblyProduct("SoulcatcherCrossbowFix")]
[assembly: AssemblyTitle("SoulcatcherCrossbowFix")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.3.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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 SoulcatcherCrossbowFix
{
	[BepInPlugin("ru.custom.soulcatcher_crossbowfix", "Soulcatcher Crossbow Fix", "1.0.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		[HarmonyPatch(typeof(SEMan), "ModifyAttack")]
		private static class SEMan_ModifyAttack_Patch
		{
			private static void Postfix(SEMan __instance, SkillType skill, ref HitData hitData)
			{
				try
				{
					if (!_cfgEnableDamage.Value)
					{
						return;
					}
					object? obj = _fiSEMan_m_character?.GetValue(__instance);
					object? obj2 = ((obj is Character) ? obj : null);
					Player val = (Player)((obj2 is Player) ? obj2 : null);
					if (val == null)
					{
						return;
					}
					ItemData currentWeapon = ((Humanoid)val).GetCurrentWeapon();
					if (!IsCrossbow(currentWeapon))
					{
						return;
					}
					float effectPercent = GetEffectPercent(val, _cfgEffectNameHare?.Value);
					if (effectPercent <= 0f)
					{
						DebugLogRate("[DMG] Effect '" + _cfgEffectNameHare?.Value + "' value=0 (no gem or not applied).");
						return;
					}
					float num = 1f + effectPercent / 100f;
					MultiplyDamage(ref hitData.m_damage, num);
					object arg = effectPercent;
					object arg2 = num;
					object arg3;
					if (currentWeapon == null)
					{
						arg3 = null;
					}
					else
					{
						GameObject dropPrefab = currentWeapon.m_dropPrefab;
						arg3 = ((dropPrefab != null) ? ((Object)dropPrefab).name : null);
					}
					DebugLogRate($"[DMG] Applied Hare. value={arg:0.###}% mult={arg2:0.###} weapon={arg3}");
					DebugDumpWeaponCustomDataRate(currentWeapon);
				}
				catch (Exception arg4)
				{
					LogError($"[DMG] ModifyAttack failed: {arg4}");
				}
			}
		}

		[HarmonyPatch(typeof(ItemData), "GetWeaponLoadingTime")]
		private static class ItemData_GetWeaponLoadingTime_Patch
		{
			private static void Postfix(ItemData __instance, ref float __result)
			{
				try
				{
					if (!_cfgEnableReload.Value)
					{
						return;
					}
					Player localPlayer = Player.m_localPlayer;
					if ((Object)(object)localPlayer == (Object)null || __instance != ((Humanoid)localPlayer).GetCurrentWeapon() || !IsCrossbow(__instance))
					{
						return;
					}
					float effectPercent = GetEffectPercent(localPlayer, _cfgEffectNameDverger?.Value);
					if (effectPercent <= 0f)
					{
						DebugLogRate("[RLD] Effect '" + _cfgEffectNameDverger?.Value + "' value=0 (no gem or not applied).");
						return;
					}
					float num = Mathf.Clamp01(1f - effectPercent / 100f);
					float num2 = __result;
					__result *= num;
					object[] obj = new object[5] { effectPercent, num, num2, __result, null };
					object obj2;
					if (__instance == null)
					{
						obj2 = null;
					}
					else
					{
						GameObject dropPrefab = __instance.m_dropPrefab;
						obj2 = ((dropPrefab != null) ? ((Object)dropPrefab).name : null);
					}
					obj[4] = obj2;
					DebugLogRate(string.Format("[RLD] Applied Dverger. value={0:0.###}% timeMult={1:0.###} loading {2:0.###}->{3:0.###} weapon={4}", obj));
					DebugDumpWeaponCustomDataRate(__instance);
				}
				catch (Exception arg)
				{
					LogError($"[RLD] GetWeaponLoadingTime failed: {arg}");
				}
			}
		}

		private Harmony _harmony;

		private static ConfigEntry<bool> _cfgEnableDamage;

		private static ConfigEntry<bool> _cfgEnableReload;

		private static ConfigEntry<bool> _cfgDebug;

		private static ConfigEntry<bool> _cfgDebugDumpWeaponCustomData;

		private static ConfigEntry<bool> _cfgDebugDumpDiscovery;

		private static ConfigEntry<float> _cfgDebugMinIntervalSec;

		private static ConfigEntry<string> _cfgEffectNameHare;

		private static ConfigEntry<string> _cfgEffectNameDverger;

		private static float _nextLogTime;

		private const string DefaultEffectHare = "Hare Soul Power";

		private const string DefaultEffectDverger = "Dverger Soul Power";

		private static bool _resolved;

		private static MethodInfo _miGetEffectPowerGenericDef;

		private static Type _cfgTypeHare;

		private static Type _cfgTypeDverger;

		private static FieldInfo _fiSEMan_m_character;

		private void Awake()
		{
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Expected O, but got Unknown
			_cfgEnableDamage = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableHareDamageBonus", true, "Enable Hare gem damage bonus for crossbows.");
			_cfgEnableReload = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableDvergerReloadBonus", true, "Enable Dverger gem reload time reduction for crossbows.");
			_cfgEffectNameHare = ((BaseUnityPlugin)this).Config.Bind<string>("Effects", "HareEffectName", "Hare Soul Power", "Jewelcrafting effect name for Hare gem.");
			_cfgEffectNameDverger = ((BaseUnityPlugin)this).Config.Bind<string>("Effects", "DvergerEffectName", "Dverger Soul Power", "Jewelcrafting effect name for Dverger gem.");
			_cfgDebug = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "DebugEnabled", false, "Enable verbose debug logging.");
			_cfgDebugDumpWeaponCustomData = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "DumpWeaponCustomData", false, "Dump current weapon m_customData (spammy).");
			_cfgDebugDumpDiscovery = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "DumpDiscovery", true, "Log discovery of GetEffectPower method and config types.");
			_cfgDebugMinIntervalSec = ((BaseUnityPlugin)this).Config.Bind<float>("Debug", "MinLogIntervalSeconds", 0.5f, "Rate limit for repeated debug logs.");
			_fiSEMan_m_character = AccessTools.Field(typeof(SEMan), "m_character");
			ResolveJewelcraftingBindings();
			_harmony = new Harmony("ru.custom.soulcatcher_crossbowfix");
			_harmony.PatchAll();
			LogInfo("[Init] Loaded v1.1.4 (Client-side, fixed SEMan private field access)");
		}

		private static bool IsCrossbow(ItemData weapon)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Invalid comparison between Unknown and I4
			if (weapon?.m_shared == null)
			{
				return false;
			}
			if ((int)weapon.m_shared.m_skillType == 14)
			{
				return true;
			}
			string text = (Object.op_Implicit((Object)(object)weapon.m_dropPrefab) ? ((Object)weapon.m_dropPrefab).name : null);
			if (string.IsNullOrEmpty(text))
			{
				return false;
			}
			return text.StartsWith("Crossbow", StringComparison.OrdinalIgnoreCase);
		}

		private static void MultiplyDamage(ref DamageTypes dmg, float mult)
		{
			dmg.m_damage *= mult;
			dmg.m_pierce *= mult;
			dmg.m_blunt *= mult;
			dmg.m_slash *= mult;
			dmg.m_fire *= mult;
			dmg.m_frost *= mult;
			dmg.m_lightning *= mult;
			dmg.m_poison *= mult;
			dmg.m_spirit *= mult;
			dmg.m_chop *= mult;
			dmg.m_pickaxe *= mult;
		}

		private static float GetEffectPercent(Player player, string effectName)
		{
			if ((Object)(object)player == (Object)null)
			{
				return 0f;
			}
			if (string.IsNullOrWhiteSpace(effectName))
			{
				return 0f;
			}
			ResolveJewelcraftingBindings();
			if (_miGetEffectPowerGenericDef == null)
			{
				DebugLogRate("[JC] GetEffectPower<T> not resolved.");
				return 0f;
			}
			Type type = null;
			if (string.Equals(effectName, _cfgEffectNameHare?.Value, StringComparison.OrdinalIgnoreCase))
			{
				type = _cfgTypeHare;
			}
			else if (string.Equals(effectName, _cfgEffectNameDverger?.Value, StringComparison.OrdinalIgnoreCase))
			{
				type = _cfgTypeDverger;
			}
			if (type == null)
			{
				if (effectName.IndexOf("Hare", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					type = _cfgTypeHare;
				}
				if (effectName.IndexOf("Dver", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					type = _cfgTypeDverger;
				}
			}
			if (type == null)
			{
				DebugLogRate("[JC] Config type not resolved for effect '" + effectName + "'.");
				return 0f;
			}
			try
			{
				object obj = _miGetEffectPowerGenericDef.MakeGenericMethod(type).Invoke(null, new object[2] { player, effectName });
				if (obj == null)
				{
					return 0f;
				}
				float num = ExtractFirstFloat(type, obj);
				ConfigEntry<bool> cfgDebug = _cfgDebug;
				if (cfgDebug != null && cfgDebug.Value)
				{
					ConfigEntry<bool> cfgDebugDumpDiscovery = _cfgDebugDumpDiscovery;
					if (cfgDebugDumpDiscovery != null && cfgDebugDumpDiscovery.Value)
					{
						LogInfo($"[JC] Effect '{effectName}' cfgType='{type.FullName}' value={num:0.###}");
					}
				}
				return num;
			}
			catch (Exception ex)
			{
				DebugLogRate("[JC] GetEffectPower invoke failed: " + ex.GetType().Name + ": " + ex.Message);
				return 0f;
			}
		}

		private static float ExtractFirstFloat(Type cfgType, object cfgObj)
		{
			try
			{
				FieldInfo field = cfgType.GetField("Value", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (field != null && field.FieldType == typeof(float))
				{
					return (float)field.GetValue(cfgObj);
				}
				FieldInfo field2 = cfgType.GetField("Power", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (field2 != null && field2.FieldType == typeof(float))
				{
					return (float)field2.GetValue(cfgObj);
				}
				FieldInfo fieldInfo = cfgType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((FieldInfo x) => x.FieldType == typeof(float));
				if (fieldInfo != null)
				{
					return (float)fieldInfo.GetValue(cfgObj);
				}
				PropertyInfo propertyInfo = cfgType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((PropertyInfo x) => x.PropertyType == typeof(float) && x.GetIndexParameters().Length == 0);
				if (propertyInfo != null)
				{
					return (float)propertyInfo.GetValue(cfgObj, null);
				}
			}
			catch
			{
			}
			return 0f;
		}

		private static void ResolveJewelcraftingBindings()
		{
			if (_resolved)
			{
				return;
			}
			_resolved = true;
			try
			{
				foreach (Assembly item in from a in AppDomain.CurrentDomain.GetAssemblies()
					orderby (a.GetName().Name ?? "").IndexOf("Jewelcrafting", StringComparison.OrdinalIgnoreCase) >= 0 descending
					select a)
				{
					Type[] types;
					try
					{
						types = item.GetTypes();
					}
					catch
					{
						continue;
					}
					Type[] array = types;
					foreach (Type type in array)
					{
						if (type == null)
						{
							continue;
						}
						MethodInfo[] methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
						foreach (MethodInfo methodInfo in methods)
						{
							if (!(methodInfo.Name != "GetEffectPower") && methodInfo.IsGenericMethodDefinition)
							{
								ParameterInfo[] parameters = methodInfo.GetParameters();
								if (parameters.Length == 2 && !(parameters[0].ParameterType != typeof(Player)) && !(parameters[1].ParameterType != typeof(string)))
								{
									_miGetEffectPowerGenericDef = methodInfo;
									goto end_IL_0118;
								}
							}
						}
					}
					continue;
					end_IL_0118:
					break;
				}
				ConfigEntry<bool> cfgDebug = _cfgDebug;
				if (cfgDebug != null && cfgDebug.Value)
				{
					ConfigEntry<bool> cfgDebugDumpDiscovery = _cfgDebugDumpDiscovery;
					if (cfgDebugDumpDiscovery != null && cfgDebugDumpDiscovery.Value)
					{
						LogInfo((_miGetEffectPowerGenericDef != null) ? ("[DISCOVERY] Found GetEffectPower<T>: " + _miGetEffectPowerGenericDef.DeclaringType?.FullName + "::" + _miGetEffectPowerGenericDef.Name) : "[DISCOVERY] GetEffectPower<T> not found.");
					}
				}
			}
			catch (Exception arg)
			{
				ConfigEntry<bool> cfgDebug2 = _cfgDebug;
				if (cfgDebug2 != null && cfgDebug2.Value)
				{
					LogError($"[DISCOVERY] GetEffectPower<T> search failed: {arg}");
				}
			}
			try
			{
				_cfgTypeHare = FindConfigTypeByFragments(new string[3] { "Hare", "Soul", "Power" });
				_cfgTypeDverger = FindConfigTypeByFragments(new string[3] { "Dver", "Soul", "Power" });
				ConfigEntry<bool> cfgDebug3 = _cfgDebug;
				if (cfgDebug3 != null && cfgDebug3.Value)
				{
					ConfigEntry<bool> cfgDebugDumpDiscovery2 = _cfgDebugDumpDiscovery;
					if (cfgDebugDumpDiscovery2 != null && cfgDebugDumpDiscovery2.Value)
					{
						LogInfo("[DISCOVERY] Hare cfgType: " + (_cfgTypeHare?.FullName ?? "NOT FOUND"));
						LogInfo("[DISCOVERY] Dverger cfgType: " + (_cfgTypeDverger?.FullName ?? "NOT FOUND"));
					}
				}
			}
			catch (Exception arg2)
			{
				ConfigEntry<bool> cfgDebug4 = _cfgDebug;
				if (cfgDebug4 != null && cfgDebug4.Value)
				{
					LogError($"[DISCOVERY] Config type search failed: {arg2}");
				}
			}
		}

		private static Type FindConfigTypeByFragments(string[] fragments)
		{
			foreach (Assembly item in from a in AppDomain.CurrentDomain.GetAssemblies()
				orderby (a.GetName().Name ?? "").IndexOf("Soulcatcher", StringComparison.OrdinalIgnoreCase) >= 0 descending
				select a)
			{
				Type[] types;
				try
				{
					types = item.GetTypes();
				}
				catch
				{
					continue;
				}
				Type[] array = types;
				foreach (Type type in array)
				{
					if ((object)type == null || type.FullName == null || !string.Equals(type.Name, "Config", StringComparison.OrdinalIgnoreCase))
					{
						continue;
					}
					bool flag = true;
					for (int j = 0; j < fragments.Length; j++)
					{
						if (type.FullName.IndexOf(fragments[j], StringComparison.OrdinalIgnoreCase) < 0)
						{
							flag = false;
							break;
						}
					}
					if (flag)
					{
						return type;
					}
				}
			}
			return null;
		}

		private static void LogInfo(string msg)
		{
			try
			{
				Logger.CreateLogSource("SoulcatcherCrossbowFix").LogInfo((object)msg);
			}
			catch
			{
				Debug.Log((object)msg);
			}
		}

		private static void LogError(string msg)
		{
			try
			{
				Logger.CreateLogSource("SoulcatcherCrossbowFix").LogError((object)msg);
			}
			catch
			{
				Debug.LogError((object)msg);
			}
		}

		private static void DebugLogRate(string msg)
		{
			if (_cfgDebug != null && _cfgDebug.Value)
			{
				float num = ((_cfgDebugMinIntervalSec != null) ? Mathf.Max(0.05f, _cfgDebugMinIntervalSec.Value) : 0.5f);
				if (!(Time.time < _nextLogTime))
				{
					_nextLogTime = Time.time + num;
					LogInfo(msg);
				}
			}
		}

		private static void DebugDumpWeaponCustomDataRate(ItemData weapon)
		{
			if (_cfgDebug == null || !_cfgDebug.Value || _cfgDebugDumpWeaponCustomData == null || !_cfgDebugDumpWeaponCustomData.Value || weapon == null)
			{
				return;
			}
			if (_cfgDebugMinIntervalSec != null)
			{
				Mathf.Max(0.05f, _cfgDebugMinIntervalSec.Value);
			}
			if (Time.time < _nextLogTime)
			{
				return;
			}
			if (weapon.m_customData == null || weapon.m_customData.Count == 0)
			{
				LogInfo("[DBG] weapon.m_customData is empty");
				return;
			}
			foreach (KeyValuePair<string, string> customDatum in weapon.m_customData)
			{
				string text = customDatum.Value ?? "";
				if (text.Length > 160)
				{
					text = text.Substring(0, 160) + "...";
				}
				LogInfo("[DBG] customData: '" + customDatum.Key + "' = '" + text + "'");
			}
		}
	}
}