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 + "'");
}
}
}
}