Decompiled source of StickyGun v1.0.0

BepInEx/plugins/StickyGun/StickyGun.dll

Decompiled 9 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using StickyGun.Patches;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp-firstpass")]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: IgnoresAccessChecksTo("Autodesk.Fbx")]
[assembly: IgnoresAccessChecksTo("Discord.Sdk")]
[assembly: IgnoresAccessChecksTo("Domain_Reload")]
[assembly: IgnoresAccessChecksTo("Facepunch.Steamworks.Win64")]
[assembly: IgnoresAccessChecksTo("FbxBuildTestAssets")]
[assembly: IgnoresAccessChecksTo("Klattersynth")]
[assembly: IgnoresAccessChecksTo("Photon3Unity3D")]
[assembly: IgnoresAccessChecksTo("PhotonChat")]
[assembly: IgnoresAccessChecksTo("PhotonRealtime")]
[assembly: IgnoresAccessChecksTo("PhotonUnityNetworking")]
[assembly: IgnoresAccessChecksTo("PhotonUnityNetworking.Utilities")]
[assembly: IgnoresAccessChecksTo("PhotonVoice.API")]
[assembly: IgnoresAccessChecksTo("PhotonVoice")]
[assembly: IgnoresAccessChecksTo("PhotonVoice.PUN")]
[assembly: IgnoresAccessChecksTo("SingularityGroup.HotReload.Runtime.Public")]
[assembly: IgnoresAccessChecksTo("Sirenix.OdinInspector.Attributes")]
[assembly: IgnoresAccessChecksTo("Sirenix.OdinInspector.Modules.Unity.Addressables")]
[assembly: IgnoresAccessChecksTo("Sirenix.OdinInspector.Modules.UnityLocalization")]
[assembly: IgnoresAccessChecksTo("Sirenix.Serialization.Config")]
[assembly: IgnoresAccessChecksTo("Sirenix.Serialization")]
[assembly: IgnoresAccessChecksTo("Sirenix.Utilities")]
[assembly: IgnoresAccessChecksTo("Unity.Addressables")]
[assembly: IgnoresAccessChecksTo("Unity.AI.Navigation")]
[assembly: IgnoresAccessChecksTo("Unity.Burst")]
[assembly: IgnoresAccessChecksTo("Unity.Burst.Unsafe")]
[assembly: IgnoresAccessChecksTo("Unity.Collections")]
[assembly: IgnoresAccessChecksTo("Unity.Collections.LowLevel.ILSupport")]
[assembly: IgnoresAccessChecksTo("Unity.Formats.Fbx.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.InputSystem")]
[assembly: IgnoresAccessChecksTo("Unity.InputSystem.ForUI")]
[assembly: IgnoresAccessChecksTo("Unity.InternalAPIEngineBridge.013")]
[assembly: IgnoresAccessChecksTo("Unity.Localization")]
[assembly: IgnoresAccessChecksTo("Unity.Mathematics")]
[assembly: IgnoresAccessChecksTo("Unity.MemoryProfiler")]
[assembly: IgnoresAccessChecksTo("Unity.Postprocessing.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.Profiling.Core")]
[assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.Core.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.Core.ShaderLibrary")]
[assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary")]
[assembly: IgnoresAccessChecksTo("Unity.ResourceManager")]
[assembly: IgnoresAccessChecksTo("Unity.ScriptableBuildPipeline")]
[assembly: IgnoresAccessChecksTo("Unity.Splines")]
[assembly: IgnoresAccessChecksTo("Unity.TextMeshPro")]
[assembly: IgnoresAccessChecksTo("Unity.Timeline")]
[assembly: IgnoresAccessChecksTo("Unity.VisualScripting.Antlr3.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.VisualScripting.Core")]
[assembly: IgnoresAccessChecksTo("Unity.VisualScripting.Flow")]
[assembly: IgnoresAccessChecksTo("Unity.VisualScripting.State")]
[assembly: IgnoresAccessChecksTo("UnityEngine.ARModule")]
[assembly: IgnoresAccessChecksTo("UnityEngine.NVIDIAModule")]
[assembly: IgnoresAccessChecksTo("UnityEngine.UI")]
[assembly: IgnoresAccessChecksTo("websocket-sharp")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("StickyGun")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+22f0d653d9a051d42d5a5e40d246ad57a90d0539")]
[assembly: AssemblyProduct("StickyGun")]
[assembly: AssemblyTitle("StickyGun")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[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 StickyGun
{
	internal static class GunCheck
	{
		internal static bool IsProtectedGun(PhysGrabObject obj)
		{
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Invalid comparison between Unknown and I4
			if ((Object)(object)obj == (Object)null)
			{
				return false;
			}
			if ((Object)(object)((Component)obj).GetComponentInChildren<ItemCartCannonMain>(true) != (Object)null)
			{
				return false;
			}
			if (StickyGunPlugin.IncludeModdedGuns.Value && (Object)(object)((Component)obj).GetComponentInChildren<ItemGun>(true) != (Object)null)
			{
				return true;
			}
			ItemAttributes component = ((Component)obj).GetComponent<ItemAttributes>();
			if ((Object)(object)component != (Object)null && (int)component.itemType == 9)
			{
				return true;
			}
			return false;
		}

		internal static string Describe(PhysGrabObject obj)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)obj == (Object)null)
			{
				return "<null>";
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append(((Object)obj).name);
			ItemAttributes component = ((Component)obj).GetComponent<ItemAttributes>();
			if ((Object)(object)component != (Object)null)
			{
				stringBuilder.Append(" | itemType=").Append(component.itemType);
				if (!string.IsNullOrEmpty(component.instanceName))
				{
					stringBuilder.Append(" | instance=").Append(component.instanceName);
				}
			}
			else
			{
				stringBuilder.Append(" | no ItemAttributes");
			}
			stringBuilder.Append(" | ItemGun=").Append((Object)(object)((Component)obj).GetComponentInChildren<ItemGun>(true) != (Object)null);
			return stringBuilder.ToString();
		}

		internal static string ComponentList(PhysGrabObject obj)
		{
			if ((Object)(object)obj == (Object)null)
			{
				return "<null>";
			}
			StringBuilder stringBuilder = new StringBuilder();
			Component[] components = ((Component)obj).GetComponents<Component>();
			for (int i = 0; i < components.Length; i++)
			{
				if (!((Object)(object)components[i] == (Object)null))
				{
					if (stringBuilder.Length > 0)
					{
						stringBuilder.Append(", ");
					}
					stringBuilder.Append(((object)components[i]).GetType().Name);
				}
			}
			return stringBuilder.ToString();
		}
	}
	[BepInPlugin("com.sinan.stickygun", "StickyGun", "1.0.0")]
	public class StickyGunPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.sinan.stickygun";

		public const string PluginName = "StickyGun";

		public const string PluginVersion = "1.0.0";

		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<bool> IncludeModdedGuns;

		internal static ConfigEntry<bool> DebugLogging;

		internal static ConfigEntry<bool> DiagnosticMode;

		public const string ConfigFileName = "StickyGun.cfg";

		private PhysGrabObject watchedGun;

		internal static bool Diagnostics
		{
			get
			{
				if (DiagnosticMode == null || !DiagnosticMode.Value)
				{
					if (DebugLogging != null)
					{
						return DebugLogging.Value;
					}
					return false;
				}
				return true;
			}
		}

		private void Awake()
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			ConfigFile val = new ConfigFile(Path.Combine(Paths.ConfigPath, "StickyGun.cfg"), true, ((BaseUnityPlugin)this).Info.Metadata);
			Enabled = val.Bind<bool>("General", "Enabled", true, "Master on/off. When false StickyGun does nothing and the game behaves exactly as vanilla.");
			IncludeModdedGuns = val.Bind<bool>("General", "IncludeModdedGuns", true, "When true, anything carrying the game's ItemGun component counts as a gun - this is what makes guns from other mods work too. When false, only items the game itself labels as item type 'gun' are protected.");
			DebugLogging = val.Bind<bool>("General", "DebugLogging", false, "Logs every forced release involving a gun, and why StickyGun did or did not step in. Useful while testing, noisy during normal play.");
			DiagnosticMode = val.Bind<bool>("General", "DiagnosticMode", false, "Logs a line each time StickyGun stops a gun being knocked out of your hands. Handy if you want to confirm the mod is doing something; off by default so your log stays clean.");
			try
			{
				new Harmony("com.sinan.stickygun").PatchAll();
				Log.LogInfo((object)"[StickyGun] loaded");
				Log.LogInfo((object)("[StickyGun] settings: Enabled=" + Enabled.Value + " IncludeModdedGuns=" + IncludeModdedGuns.Value + " DiagnosticMode=" + DiagnosticMode.Value + " DebugLogging=" + DebugLogging.Value));
			}
			catch (Exception ex)
			{
				Log.LogError((object)("[StickyGun] failed to apply patches, mod is inactive: " + ex));
			}
		}

		private void Update()
		{
			if (Log == null || DebugLogging == null || !DebugLogging.Value)
			{
				watchedGun = null;
				return;
			}
			try
			{
				PhysGrabber instance = PhysGrabber.instance;
				if ((Object)(object)instance == (Object)null || !instance.isLocal)
				{
					return;
				}
				PhysGrabObject val = (instance.grabbed ? instance.grabbedPhysGrabObject : null);
				if ((Object)(object)val != (Object)null && GunCheck.IsProtectedGun(val))
				{
					if ((Object)(object)watchedGun != (Object)(object)val)
					{
						watchedGun = val;
						Log.LogInfo((object)("[StickyGun][diag] now holding gun on beam: '" + ((Object)val).name + "' | moonLevel=" + SafeMoonLevel()));
					}
				}
				else if ((Object)(object)watchedGun != (Object)null)
				{
					string text = "<destroyed>";
					bool flag = false;
					try
					{
						text = ((Object)watchedGun).name;
						ItemEquippable component = ((Component)watchedGun).GetComponent<ItemEquippable>();
						flag = (Object)(object)component != (Object)null && component.IsEquipped();
					}
					catch
					{
					}
					bool flag2 = PhysGrabberReleasePatch.lastGunReleaseFrame >= Time.frameCount - 1;
					Log.LogWarning((object)("[StickyGun][diag] gun '" + text + "' LEFT THE BEAM | viaReleaseObject=" + flag2 + " | wentIntoASlot=" + flag + " | moonLevel=" + SafeMoonLevel()));
					if (!flag2 && !flag)
					{
						Log.LogWarning((object)"[StickyGun][diag]   ^^ NOT via ReleaseObject and NOT equipped. The game used a route we have not patched. Send me this log.");
					}
					watchedGun = null;
				}
			}
			catch (Exception ex)
			{
				Log.LogError((object)("[StickyGun][diag] watcher error: " + ex));
			}
		}

		private static int SafeMoonLevel()
		{
			try
			{
				return SemiFunc.MoonLevel();
			}
			catch
			{
				return -1;
			}
		}
	}
}
namespace StickyGun.Patches
{
	internal static class Diag
	{
		internal static string CallChain(int maxFrames = 8)
		{
			try
			{
				StackTrace stackTrace = new StackTrace(fNeedFileInfo: false);
				StringBuilder stringBuilder = new StringBuilder();
				int num = 0;
				for (int i = 2; i < stackTrace.FrameCount; i++)
				{
					if (num >= maxFrames)
					{
						break;
					}
					MethodBase methodBase = stackTrace.GetFrame(i)?.GetMethod();
					if (methodBase == null)
					{
						continue;
					}
					string text = ((methodBase.DeclaringType != null) ? methodBase.DeclaringType.Name : "?");
					if (!text.Contains("Harmony") && !text.StartsWith("StickyGun"))
					{
						if (num > 0)
						{
							stringBuilder.Append(" <- ");
						}
						stringBuilder.Append(text).Append('.').Append(methodBase.Name);
						num++;
					}
				}
				return (num > 0) ? stringBuilder.ToString() : "<no game frames found>";
			}
			catch (Exception ex)
			{
				return "<could not read call chain: " + ex.Message + ">";
			}
		}
	}
	[HarmonyPatch(typeof(Inventory), "ForceUnequip")]
	internal static class InventoryForceUnequipDiagnostic
	{
		private static void Prefix()
		{
			try
			{
				if (StickyGunPlugin.DebugLogging.Value)
				{
					StickyGunPlugin.Log.LogWarning((object)("[StickyGun][diag] Inventory.ForceUnequip fired - called by: " + Diag.CallChain()));
				}
			}
			catch (Exception ex)
			{
				StickyGunPlugin.Log.LogError((object)("[StickyGun][diag] " + ex));
			}
		}
	}
	[HarmonyPatch(typeof(ItemEquippable), "ForceUnequip")]
	internal static class ItemForceUnequipDiagnostic
	{
		private static void Prefix(ItemEquippable __instance)
		{
			try
			{
				if (StickyGunPlugin.DebugLogging.Value)
				{
					StickyGunPlugin.Log.LogWarning((object)("[StickyGun][diag] ItemEquippable.ForceUnequip on '" + (((Object)(object)__instance != (Object)null) ? ((Object)__instance).name : "<null>") + "' - called by: " + Diag.CallChain()));
				}
			}
			catch (Exception ex)
			{
				StickyGunPlugin.Log.LogError((object)("[StickyGun][diag] " + ex));
			}
		}
	}
	[HarmonyPatch(typeof(HurtCollider), "PhysObjectHurt")]
	internal static class HurtColliderPhysObjectHurtPatch
	{
		private static int lastLoggedId;

		private static float lastLoggedTime;

		private static void Prefix(PhysGrabObject physGrabObject, float hitForce)
		{
			try
			{
				if (!StickyGunPlugin.Enabled.Value || (Object)(object)physGrabObject == (Object)null || physGrabObject.playerGrabbing == null || physGrabObject.playerGrabbing.Count == 0 || !GunCheck.IsProtectedGun(physGrabObject))
				{
					return;
				}
				physGrabObject.OverrideKnockOutOfGrabDisable(0.1f);
				if (StickyGunPlugin.Diagnostics)
				{
					int instanceID = ((Object)physGrabObject).GetInstanceID();
					if (instanceID != lastLoggedId || Time.time - lastLoggedTime > 1f)
					{
						lastLoggedId = instanceID;
						lastLoggedTime = Time.time;
						StickyGunPlugin.Log.LogInfo((object)("[StickyGun] PROTECTED '" + ((Object)physGrabObject).name + "' from being knocked out of your hands (hitForce=" + hitForce + ")"));
					}
				}
			}
			catch (Exception ex)
			{
				StickyGunPlugin.Log.LogError((object)("[StickyGun] knock-out patch error: " + ex));
			}
		}
	}
	[HarmonyPatch(typeof(PhysGrabber), "ReleaseObject", new Type[]
	{
		typeof(int),
		typeof(float)
	})]
	internal static class PhysGrabberReleasePatch
	{
		private static readonly HashSet<int> alreadyDescribed = new HashSet<int>();

		internal static int lastGunReleaseFrame = -1;

		private static bool Prefix(PhysGrabber __instance, int _releaseObjectViewID, float _disableTimer)
		{
			try
			{
				PhysGrabObject val = (((Object)(object)__instance != (Object)null && __instance.grabbed) ? __instance.grabbedPhysGrabObject : null);
				bool flag = (Object)(object)__instance != (Object)null && __instance.isLocal && (Object)(object)val != (Object)null && GunCheck.IsProtectedGun(val);
				if (flag && StickyGunPlugin.DebugLogging.Value)
				{
					lastGunReleaseFrame = Time.frameCount;
					if (alreadyDescribed.Add(((Object)val).GetInstanceID()))
					{
						StickyGunPlugin.Log.LogInfo((object)("[StickyGun] gun detected: " + GunCheck.Describe(val)));
						StickyGunPlugin.Log.LogInfo((object)("[StickyGun]   components: " + GunCheck.ComponentList(val)));
					}
					ItemEquippable component = ((Component)val).GetComponent<ItemEquippable>();
					StickyGunPlugin.Log.LogWarning((object)("[StickyGun][diag] ReleaseObject on gun '" + ((Object)val).name + "' | releaseViewID=" + _releaseObjectViewID + " | disableTimer=" + _disableTimer + " | equipped=" + ((Object)(object)component != (Object)null && component.IsEquipped()) + " | equipping=" + ((Object)(object)component != (Object)null && component.isEquipping) + " | grabbers=" + ((val.playerGrabbing != null) ? val.playerGrabbing.Count : (-1)) + " | moonLevel=" + SafeMoonLevel() + " | called by: " + Diag.CallChain()));
				}
				if (!StickyGunPlugin.Enabled.Value)
				{
					return true;
				}
				if (_releaseObjectViewID == -1)
				{
					return true;
				}
				if (!flag)
				{
					return true;
				}
				ItemEquippable component2 = ((Component)val).GetComponent<ItemEquippable>();
				if ((Object)(object)component2 != (Object)null && (component2.IsEquipped() || component2.isEquipping))
				{
					return true;
				}
				if (val.playerGrabbing != null && val.playerGrabbing.Count > 1)
				{
					return true;
				}
				if (StickyGunPlugin.Diagnostics)
				{
					StickyGunPlugin.Log.LogInfo((object)("[StickyGun] BLOCKED forced drop of " + ((Object)val).name + " (releaseViewID=" + _releaseObjectViewID + ")"));
				}
				return false;
			}
			catch (Exception ex)
			{
				StickyGunPlugin.Log.LogError((object)("[StickyGun] patch error, letting the game continue: " + ex));
				return true;
			}
		}

		private static int SafeMoonLevel()
		{
			try
			{
				return SemiFunc.MoonLevel();
			}
			catch
			{
				return -1;
			}
		}
	}
	[HarmonyPatch(typeof(PhysGrabber), "ReleaseObjectRPC")]
	internal static class PhysGrabberReleaseRpcPatch
	{
		private static bool Prefix(PhysGrabber __instance, int _releaseObjectViewID)
		{
			try
			{
				if (!StickyGunPlugin.Enabled.Value)
				{
					return true;
				}
				if (_releaseObjectViewID != -1)
				{
					return true;
				}
				if ((Object)(object)__instance == (Object)null || !__instance.isLocal || !__instance.grabbed)
				{
					return true;
				}
				PhysGrabObject grabbedPhysGrabObject = __instance.grabbedPhysGrabObject;
				if ((Object)(object)grabbedPhysGrabObject == (Object)null || !GunCheck.IsProtectedGun(grabbedPhysGrabObject))
				{
					return true;
				}
				ItemEquippable component = ((Component)grabbedPhysGrabObject).GetComponent<ItemEquippable>();
				if ((Object)(object)component != (Object)null && (component.IsEquipped() || component.isEquipping))
				{
					return true;
				}
				if (StickyGunPlugin.Diagnostics)
				{
					StickyGunPlugin.Log.LogInfo((object)("[StickyGun] PROTECTED '" + ((Object)grabbedPhysGrabObject).name + "' - declined a networked drop order."));
				}
				return false;
			}
			catch (Exception ex)
			{
				StickyGunPlugin.Log.LogError((object)("[StickyGun] RPC patch error, letting the game continue: " + ex));
				return true;
			}
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}