Decompiled source of HalfSwordControls v0.5.1

HalfSwordControls.dll

Decompiled 3 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BoneLib.BoneMenu;
using HalfSwordControls.Core;
using HalfSwordControls.Features.ApiProbe;
using HalfSwordControls.Features.BoneMenuUi;
using HalfSwordControls.Features.DecoupledInput;
using HalfSwordControls.Util;
using HarmonyLib;
using MelonLoader;
using MelonLoader.Preferences;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: MelonInfo(typeof(ModEntry), "HalfSwordControls", "0.5.0", "maxt8", null)]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[assembly: MelonPriority(1000)]
[assembly: HarmonyDontPatchAll]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("HalfSwordControls")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("HalfSwordControls")]
[assembly: AssemblyTitle("HalfSwordControls")]
[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 HalfSwordControls.Util
{
	internal static class FlatPlayerBridge
	{
		private sealed class Member
		{
			private readonly PropertyInfo _p;

			private readonly FieldInfo _f;

			public readonly string Description;

			private Member(PropertyInfo p, FieldInfo f)
			{
				_p = p;
				_f = f;
				Description = ((p != null) ? (p.Name + " (property " + p.PropertyType.Name + " on " + p.DeclaringType.Name + ")") : (f.Name + " (field " + f.FieldType.Name + " on " + f.DeclaringType.Name + ")"));
			}

			public static Member Find(Type type, string name)
			{
				Type type2 = type;
				while (type2 != null && type2 != typeof(object))
				{
					try
					{
						PropertyInfo property = type2.GetProperty(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
						if (property != null)
						{
							return new Member(property, null);
						}
						FieldInfo field = type2.GetField(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
						if (field != null)
						{
							return new Member(null, field);
						}
					}
					catch
					{
					}
					type2 = type2.BaseType;
				}
				return null;
			}

			public object Get(object obj)
			{
				return (_p != null) ? _p.GetValue(obj) : _f.GetValue(obj);
			}

			public void Set(object obj, object value)
			{
				if (_p != null)
				{
					_p.SetValue(obj, value);
				}
				else
				{
					_f.SetValue(obj, value);
				}
			}
		}

		private const BindingFlags Stat = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

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

		private const BindingFlags Declared = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

		private static Instance _log;

		private static bool _resolved;

		private static bool _devicesChecked;

		private static bool _devicesOk;

		private static FieldInfo _sInstance;

		private static FieldInfo _sIsReady;

		private static FieldInfo _sHmd;

		private static FieldInfo _sLeftCtl;

		private static FieldInfo _sRightCtl;

		private static FieldInfo _fCam;

		private static FieldInfo _fLeftRot;

		private static FieldInfo _fRightRot;

		private static FieldInfo _fLeftPos;

		private static FieldInfo _fRightPos;

		private static MethodInfo _resetPositions;

		private static Member _hmdRot;

		private static Member _ctlRot;

		private static Member _ctlPos;

		private static object Booter => _sInstance.GetValue(null);

		public static bool IsReady
		{
			get
			{
				if (!_resolved)
				{
					return false;
				}
				if (Booter == null)
				{
					return false;
				}
				if (!(_sIsReady.GetValue(null) is bool flag) || !flag)
				{
					return false;
				}
				return EnsureDevices();
			}
		}

		public static bool Resolve(Instance log)
		{
			if (_resolved)
			{
				return true;
			}
			_log = log;
			Type type = TypeExplorer.FindType("FlatPlayer.FlatBooter");
			if (type == null)
			{
				log.Warning("[FlatPlayerBridge] FlatPlayer.FlatBooter not found. Is FlatPlayer installed?");
				return false;
			}
			_sInstance = type.GetField("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			_sIsReady = type.GetField("IsReady", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			_sHmd = type.GetField("XRHmd", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			_sLeftCtl = type.GetField("LeftController", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			_sRightCtl = type.GetField("RightController", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			_fCam = type.GetField("cameraLastRotation", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			_fLeftRot = type.GetField("leftHandRot", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			_fRightRot = type.GetField("rightHandRot", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			_fLeftPos = type.GetField("leftHandPos", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			_fRightPos = type.GetField("rightHandPos", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			_resetPositions = type.GetMethod("ResetPositions", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
			if (_resetPositions == null)
			{
				log.Warning("[FlatPlayerBridge] FlatBooter.ResetPositions not found: Reset Hands button inactive.");
			}
			bool flag = true;
			flag &= Check(_sInstance, "Instance");
			flag &= Check(_sIsReady, "IsReady");
			flag &= Check(_sHmd, "XRHmd");
			flag &= Check(_sLeftCtl, "LeftController");
			flag &= Check(_sRightCtl, "RightController");
			flag &= Check(_fCam, "cameraLastRotation");
			flag &= Check(_fLeftRot, "leftHandRot");
			flag &= Check(_fRightRot, "rightHandRot");
			flag &= Check(_fLeftPos, "leftHandPos");
			flag = (_resolved = flag & Check(_fRightPos, "rightHandPos"));
			if (flag)
			{
				log.Msg("[FlatPlayerBridge] FlatBooter members resolved.");
			}
			return flag;
		}

		private static bool Check(object member, string name)
		{
			if (member != null)
			{
				return true;
			}
			_log.Warning("[FlatPlayerBridge] FlatBooter member not found: " + name);
			return false;
		}

		private static bool EnsureDevices()
		{
			if (_devicesChecked)
			{
				return _devicesOk;
			}
			object value = _sHmd.GetValue(null);
			object value2 = _sLeftCtl.GetValue(null);
			object value3 = _sRightCtl.GetValue(null);
			if (value == null || value2 == null || value3 == null)
			{
				return false;
			}
			_devicesChecked = true;
			_hmdRot = Member.Find(value.GetType(), "_rotation");
			_ctlRot = Member.Find(value2.GetType(), "_rotation");
			_ctlPos = Member.Find(value2.GetType(), "_position");
			_devicesOk = _hmdRot != null && _ctlRot != null && _ctlPos != null;
			if (_devicesOk)
			{
				_log.Msg("[FlatPlayerBridge] XRHmd." + _hmdRot.Description);
				_log.Msg("[FlatPlayerBridge] Controller." + _ctlRot.Description + " / " + _ctlPos.Description);
			}
			else
			{
				_log.Error("[FlatPlayerBridge] Cannot find _rotation/_position on virtual devices. Writing type dump to Desktop.");
				DumpFailure(value, value2);
			}
			return _devicesOk;
		}

		private static void DumpFailure(object hmd, object left)
		{
			try
			{
				StringBuilder stringBuilder = new StringBuilder();
				stringBuilder.AppendLine("HMD runtime type: " + hmd.GetType().FullName);
				stringBuilder.AppendLine("Controller runtime type: " + left.GetType().FullName);
				Type type = hmd.GetType();
				while (type != null && type != typeof(object))
				{
					TypeExplorer.DumpTypes(new string[1] { type.FullName }, stringBuilder);
					type = type.BaseType;
				}
				Type type2 = left.GetType();
				while (type2 != null && type2 != typeof(object))
				{
					TypeExplorer.DumpTypes(new string[1] { type2.FullName }, stringBuilder);
					type2 = type2.BaseType;
				}
				string text = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "HalfSword_BridgeDump.txt");
				File.WriteAllText(text, stringBuilder.ToString());
				_log.Msg("[FlatPlayerBridge] Dump written to " + text);
			}
			catch (Exception ex)
			{
				_log.Error("[FlatPlayerBridge] Dump failed: " + ex.Message);
			}
		}

		public static Vector2 GetCamera()
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			return (Vector2)_fCam.GetValue(Booter);
		}

		public static void SetCamera(Vector2 v)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			_fCam.SetValue(Booter, v);
		}

		public static Quaternion GetHmd()
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			return (Quaternion)_hmdRot.Get(_sHmd.GetValue(null));
		}

		public static void SetHmd(Quaternion q)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			_hmdRot.Set(_sHmd.GetValue(null), q);
		}

		public static Quaternion GetHandRot(bool right)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			return (Quaternion)(right ? _fRightRot : _fLeftRot).GetValue(Booter);
		}

		public static void SetHandRot(bool right, Quaternion q)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			(right ? _fRightRot : _fLeftRot).SetValue(Booter, q);
		}

		public static Vector3 GetHandPos(bool right)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			return (Vector3)(right ? _fRightPos : _fLeftPos).GetValue(Booter);
		}

		public static void SetHandPos(bool right, Vector3 p)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			(right ? _fRightPos : _fLeftPos).SetValue(Booter, p);
		}

		public static void ResetHands()
		{
			if (_resolved && !(_resetPositions == null))
			{
				object booter = Booter;
				if (booter != null)
				{
					_resetPositions.Invoke(booter, null);
				}
			}
		}

		public static void SetControllerPose(bool right, Quaternion rot, Vector3 pos)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			object value = (right ? _sRightCtl : _sLeftCtl).GetValue(null);
			_ctlRot.Set(value, rot);
			_ctlPos.Set(value, pos);
		}

		public static string DescribeController(bool right)
		{
			object value = (right ? _sRightCtl : _sLeftCtl).GetValue(null);
			if (value == null)
			{
				return "(no controller)";
			}
			StringBuilder stringBuilder = new StringBuilder();
			string[] array = new string[4] { "Grip", "GripButton", "GripForce", "Trigger" };
			foreach (string text in array)
			{
				Member member = Member.Find(value.GetType(), text);
				stringBuilder.Append(text).Append('=').Append((member != null) ? member.Get(value) : "?")
					.Append(' ');
			}
			return stringBuilder.ToString();
		}
	}
	internal static class MouseInput
	{
		private static bool _initTried;

		private static bool _newOk;

		private static PropertyInfo _currentProp;

		private static PropertyInfo _deltaProp;

		private static PropertyInfo _scrollProp;

		private static readonly Dictionary<Type, MethodInfo> _readCache = new Dictionary<Type, MethodInfo>();

		private static bool _warnedDelta;

		private static bool _warnedScroll;

		public static bool CameraModeActive => Input.GetKey(ModConfig.CameraKey.Value);

		public static Vector2 ReadMouseDelta()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: 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_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			if (TryReadNew(_deltaProp, out var value))
			{
				return value;
			}
			try
			{
				return new Vector2(Input.GetAxisRaw("Mouse X"), Input.GetAxisRaw("Mouse Y"));
			}
			catch (Exception ex)
			{
				if (!_warnedDelta)
				{
					_warnedDelta = true;
					MelonLogger.Warning("[MouseInput] No mouse delta source works: " + ex.GetType().Name);
				}
				return Vector2.zero;
			}
		}

		public static float ReadScrollNotches()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			if (TryReadNew(_scrollProp, out var value))
			{
				return value.y / 120f;
			}
			try
			{
				return Input.mouseScrollDelta.y;
			}
			catch (Exception ex)
			{
				if (!_warnedScroll)
				{
					_warnedScroll = true;
					MelonLogger.Warning("[MouseInput] No scroll source works: " + ex.GetType().Name);
				}
				return 0f;
			}
		}

		private static bool InitNew()
		{
			if (_initTried)
			{
				return _newOk;
			}
			_initTried = true;
			try
			{
				Type type = TypeExplorer.FindType("UnityEngine.InputSystem.Mouse");
				if (type == null)
				{
					return false;
				}
				_currentProp = type.GetProperty("current", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy);
				_deltaProp = type.GetProperty("delta", BindingFlags.Instance | BindingFlags.Public);
				_scrollProp = type.GetProperty("scroll", BindingFlags.Instance | BindingFlags.Public);
				_newOk = _currentProp != null && _deltaProp != null && _scrollProp != null;
			}
			catch
			{
				_newOk = false;
			}
			MelonLogger.Msg("[MouseInput] New Input System mouse path: " + (_newOk ? "available" : "NOT available, using legacy Input"));
			return _newOk;
		}

		private static bool TryReadNew(PropertyInfo control, out Vector2 value)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			value = default(Vector2);
			if (!InitNew() || control == null)
			{
				return false;
			}
			try
			{
				object value2 = _currentProp.GetValue(null);
				if (value2 == null)
				{
					return false;
				}
				object value3 = control.GetValue(value2);
				if (value3 == null)
				{
					return false;
				}
				Type type = value3.GetType();
				if (!_readCache.TryGetValue(type, out var value4))
				{
					value4 = type.GetMethod("ReadValue", Type.EmptyTypes);
					_readCache[type] = value4;
				}
				if (value4 == null)
				{
					return false;
				}
				if (value4.Invoke(value3, null) is Vector2 val)
				{
					value = val;
					return true;
				}
			}
			catch
			{
			}
			return false;
		}
	}
	internal static class RigAccess
	{
		private static readonly string[] RightNames = new string[4] { "m_handRt", "handRt", "rightHand", "m_rightHand" };

		private static readonly string[] LeftNames = new string[4] { "m_handLf", "handLf", "leftHand", "m_leftHand" };

		private static readonly string[] HeadNames = new string[5] { "m_head", "head", "hmdTransform", "hmd", "m_hmd" };

		private static MemberInfo _right;

		private static MemberInfo _left;

		private static MemberInfo _head;

		private static bool _dumped;

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

		public static Transform GetHand(object rig, bool right)
		{
			return right ? Read(rig, RightNames, ref _right) : Read(rig, LeftNames, ref _left);
		}

		public static Transform GetHead(object rig)
		{
			return Read(rig, HeadNames, ref _head);
		}

		private static Transform Read(object rig, string[] names, ref MemberInfo cache)
		{
			if (rig == null)
			{
				return null;
			}
			if (cache == null)
			{
				cache = Find(rig.GetType(), names);
				if (cache == null)
				{
					if (!_dumped)
					{
						_dumped = true;
						MelonLogger.Warning("[RigAccess] Could not find any of: " + string.Join(", ", names));
						Dump(rig.GetType());
					}
					return null;
				}
				MelonLogger.Msg("[RigAccess] Resolved '" + cache.Name + "' on " + rig.GetType().Name);
			}
			object obj = ((cache is PropertyInfo propertyInfo) ? propertyInfo.GetValue(rig) : ((FieldInfo)cache).GetValue(rig));
			return (Transform)((obj is Transform) ? obj : null);
		}

		private static MemberInfo Find(Type type, string[] names)
		{
			foreach (string name in names)
			{
				PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (property != null && typeof(Transform).IsAssignableFrom(property.PropertyType))
				{
					return property;
				}
				FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (field != null && typeof(Transform).IsAssignableFrom(field.FieldType))
				{
					return field;
				}
			}
			return null;
		}

		public static void Dump(Type type)
		{
			MelonLogger.Msg("[RigAccess] ---- Dump of " + type.FullName + " ----");
			Type type2 = type;
			while (type2 != null && type2 != typeof(object) && (type2.Namespace == null || !type2.Namespace.StartsWith("Il2CppInterop")))
			{
				MelonLogger.Msg("[RigAccess] class " + type2.FullName);
				PropertyInfo[] properties = type2.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (PropertyInfo propertyInfo in properties)
				{
					if (Interesting(propertyInfo.PropertyType))
					{
						MelonLogger.Msg("[RigAccess]   prop  " + propertyInfo.PropertyType.Name + " " + propertyInfo.Name);
					}
				}
				FieldInfo[] fields = type2.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (FieldInfo fieldInfo in fields)
				{
					if (Interesting(fieldInfo.FieldType))
					{
						MelonLogger.Msg("[RigAccess]   field " + fieldInfo.FieldType.Name + " " + fieldInfo.Name);
					}
				}
				type2 = type2.BaseType;
			}
			MelonLogger.Msg("[RigAccess] ---- end dump ----");
		}

		private static bool Interesting(Type t)
		{
			return typeof(Transform).IsAssignableFrom(t) || t.Name.IndexOf("Controller", StringComparison.OrdinalIgnoreCase) >= 0 || t.Name.IndexOf("Hand", StringComparison.OrdinalIgnoreCase) >= 0;
		}
	}
	internal static class TypeExplorer
	{
		private const BindingFlags F = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		public static Type FindType(string fullName)
		{
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				try
				{
					Type type = assembly.GetType(fullName, throwOnError: false);
					if (type != null)
					{
						return type;
					}
				}
				catch
				{
				}
			}
			return null;
		}

		public static void DumpTypes(IEnumerable<string> fullNames, StringBuilder sb)
		{
			foreach (string fullName in fullNames)
			{
				Type type = FindType(fullName);
				if (type == null)
				{
					sb.AppendLine("### NOT FOUND: " + fullName);
					sb.AppendLine();
				}
				else
				{
					DumpType(type, sb);
				}
			}
		}

		public static void DumpAssembly(string simpleName, StringBuilder sb)
		{
			Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => a.GetName().Name == simpleName);
			if (assembly == null)
			{
				sb.AppendLine("### ASSEMBLY NOT LOADED: " + simpleName);
				return;
			}
			Type[] array;
			try
			{
				array = assembly.GetTypes();
			}
			catch (ReflectionTypeLoadException ex)
			{
				array = ex.Types.Where((Type x) => x != null).ToArray();
			}
			sb.AppendLine("######## ASSEMBLY " + simpleName + " (" + array.Length + " types) ########");
			foreach (Type item in array.OrderBy((Type x) => x.FullName))
			{
				DumpType(item, sb);
			}
		}

		private static void DumpType(Type t, StringBuilder sb)
		{
			sb.AppendLine("### " + t.FullName + " : " + ((t.BaseType != null) ? t.BaseType.FullName : "-"));
			try
			{
				FieldInfo[] fields = t.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (FieldInfo fieldInfo in fields)
				{
					if (!Skip(fieldInfo.Name))
					{
						sb.AppendLine("  field  " + (fieldInfo.IsStatic ? "static " : "") + fieldInfo.FieldType.Name + " " + fieldInfo.Name);
					}
				}
				PropertyInfo[] properties = t.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (PropertyInfo propertyInfo in properties)
				{
					if (!Skip(propertyInfo.Name))
					{
						sb.AppendLine("  prop   " + propertyInfo.PropertyType.Name + " " + propertyInfo.Name);
					}
				}
				MethodInfo[] methods = t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (MethodInfo methodInfo in methods)
				{
					if (!methodInfo.IsSpecialName && !Skip(methodInfo.Name))
					{
						string text = string.Join(", ", from x in methodInfo.GetParameters()
							select x.ParameterType.Name + " " + x.Name);
						sb.AppendLine("  method " + (methodInfo.IsStatic ? "static " : "") + methodInfo.ReturnType.Name + " " + methodInfo.Name + "(" + text + ")");
					}
				}
			}
			catch (Exception ex)
			{
				sb.AppendLine("  (dump failed: " + ex.GetType().Name + ")");
			}
			sb.AppendLine();
		}

		private static bool Skip(string name)
		{
			return name.StartsWith("NativeFieldInfoPtr_") || name.StartsWith("NativeMethodInfoPtr_") || name.StartsWith("NativeClassPtr") || name.Contains("<");
		}
	}
}
namespace HalfSwordControls.Features.DecoupledInput
{
	internal sealed class DecoupledInputFeature : IFeature
	{
		private sealed class HandState
		{
			public bool Held;

			public Vector3 BaseEuler;

			public Vector3 BasePos;

			public float YawOff;

			public float PitchOff;

			public float XOff;

			public float YOff;

			public float Reach;

			public bool LogPending;
		}

		private readonly HandState _left = new HandState();

		private readonly HandState _right = new HandState();

		private Instance _log;

		private bool _active;

		private bool _failed;

		private int _transitionLogs;

		private bool _haveSnap;

		private Vector2 _snapCam;

		private Quaternion _snapHmd = Quaternion.identity;

		private float _snapZLeft;

		private float _snapZRight;

		private bool _orderConfirmed;

		private int _mouseMoveFrames;

		private bool _orderWarned;

		public string Name => "DecoupledInput";

		public bool Enabled => ModConfig.DecoupledInputEnabled.Value;

		public void Initialize(Harmony harmony, Instance log)
		{
			_log = log;
			if (!FlatPlayerBridge.Resolve(log))
			{
				_failed = true;
				log.Error("FlatPlayer bridge unavailable, feature inactive.");
			}
		}

		public void OnUpdate()
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Invalid comparison between Unknown and I4
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			if (_failed)
			{
				return;
			}
			if (!FlatPlayerBridge.IsReady)
			{
				ReleaseAll();
				_haveSnap = false;
				return;
			}
			bool flag = !ModConfig.RequireCursorLock.Value || (int)Cursor.lockState == 1;
			bool cameraModeActive = MouseInput.CameraModeActive;
			SetHeld(_left, flag && !cameraModeActive && Input.GetKey(ModConfig.LeftHandButton.Value), right: false);
			SetHeld(_right, flag && !cameraModeActive && Input.GetKey(ModConfig.RightHandButton.Value), right: true);
			_active = _left.Held || _right.Held;
			if (_active)
			{
				Vector2 d = MouseInput.ReadMouseDelta();
				if (((Vector2)(ref d)).sqrMagnitude > 0.25f)
				{
					_mouseMoveFrames++;
				}
				bool key = Input.GetKey(ModConfig.RotateModifierKey.Value);
				float dReach = MouseInput.ReadScrollNotches() * ModConfig.ReachPerScroll.Value;
				Integrate(_left, d, key, dReach);
				Integrate(_right, d, key, dReach);
			}
		}

		public void OnLateUpdate()
		{
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			if (_failed)
			{
				return;
			}
			try
			{
				if (!FlatPlayerBridge.IsReady)
				{
					_haveSnap = false;
					return;
				}
				if (_active && _haveSnap)
				{
					Vector2 camera = FlatPlayerBridge.GetCamera();
					if (!_orderConfirmed)
					{
						Vector2 val = camera - _snapCam;
						if (((Vector2)(ref val)).sqrMagnitude > 1E-08f)
						{
							_orderConfirmed = true;
							_log.Msg("Order confirmed: FlatPlayer moved the camera before us, restoring it.");
						}
					}
					FlatPlayerBridge.SetCamera(_snapCam);
					FlatPlayerBridge.SetHmd(_snapHmd);
					ApplyHand(_left, right: false, _snapZLeft);
					ApplyHand(_right, right: true, _snapZRight);
					if (!_orderConfirmed && !_orderWarned && _mouseMoveFrames >= 60)
					{
						_orderWarned = true;
						_log.Warning("Camera never changed while the mouse moved: FlatPlayer may run AFTER us. Send me this log.");
					}
				}
				_snapCam = FlatPlayerBridge.GetCamera();
				_snapHmd = FlatPlayerBridge.GetHmd();
				_snapZLeft = FlatPlayerBridge.GetHandPos(right: false).z;
				_snapZRight = FlatPlayerBridge.GetHandPos(right: true).z;
				_haveSnap = true;
			}
			catch (Exception ex)
			{
				_failed = true;
				_active = false;
				_log.Error("DecoupledInput disabled after error: " + ex);
			}
		}

		private void SetHeld(HandState h, bool want, bool right)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			if (want != h.Held)
			{
				if (want)
				{
					Quaternion handRot = FlatPlayerBridge.GetHandRot(right);
					h.BaseEuler = ((Quaternion)(ref handRot)).eulerAngles;
					h.BasePos = FlatPlayerBridge.GetHandPos(right);
					h.YawOff = 0f;
					h.PitchOff = 0f;
					h.XOff = 0f;
					h.YOff = 0f;
					h.Reach = h.BasePos.z;
					h.LogPending = true;
				}
				h.Held = want;
				if (_transitionLogs++ < 12)
				{
					_log.Msg((right ? "RIGHT" : "LEFT") + " hand control " + (want ? "ON" : "OFF"));
				}
			}
		}

		private static void Integrate(HandState h, Vector2 d, bool rotate, float dReach)
		{
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			if (h.Held)
			{
				bool value = ModConfig.InvertY.Value;
				if (rotate)
				{
					float value2 = ModConfig.RotSensitivity.Value;
					float value3 = ModConfig.AimYawLimit.Value;
					float value4 = ModConfig.AimPitchLimit.Value;
					h.YawOff = Mathf.Clamp(h.YawOff + d.x * value2, 0f - value3, value3);
					h.PitchOff = Mathf.Clamp(h.PitchOff + (value ? d.y : (0f - d.y)) * value2, 0f - value4, value4);
				}
				else
				{
					float value5 = ModConfig.PosSensitivity.Value;
					float value6 = ModConfig.AimXLimit.Value;
					float value7 = ModConfig.AimYLimit.Value;
					h.XOff = Mathf.Clamp(h.XOff + d.x * value5, 0f - value6, value6);
					h.YOff = Mathf.Clamp(h.YOff + (value ? (0f - d.y) : d.y) * value5, 0f - value7, value7);
				}
				h.Reach = Mathf.Clamp(h.Reach + dReach, 0f, 1f);
			}
		}

		private void ReleaseAll()
		{
			_left.Held = false;
			_right.Held = false;
			_active = false;
		}

		public void Shutdown()
		{
			ReleaseAll();
		}

		private void ApplyHand(HandState h, bool right, float snapZ)
		{
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			Quaternion val;
			Vector3 handPos = default(Vector3);
			if (h.Held)
			{
				val = Quaternion.Euler(h.BaseEuler.x + h.PitchOff, h.BaseEuler.y + h.YawOff, h.BaseEuler.z);
				((Vector3)(ref handPos))..ctor(h.BasePos.x + h.XOff, h.BasePos.y + h.YOff, h.Reach);
				FlatPlayerBridge.SetHandRot(right, val);
				FlatPlayerBridge.SetHandPos(right, handPos);
			}
			else
			{
				val = FlatPlayerBridge.GetHandRot(right);
				handPos = FlatPlayerBridge.GetHandPos(right);
				handPos.z = snapZ;
				FlatPlayerBridge.SetHandPos(right, handPos);
			}
			FlatPlayerBridge.SetControllerPose(right, _snapHmd * val, _snapHmd * handPos);
			if (h.LogPending && h.Held)
			{
				h.LogPending = false;
				_log.Msg((right ? "RIGHT" : "LEFT") + " controller input at press: " + FlatPlayerBridge.DescribeController(right));
			}
		}
	}
}
namespace HalfSwordControls.Features.BoneMenuUi
{
	internal sealed class BoneMenuFeature : IFeature
	{
		private const float SaveDelay = 1f;

		private Instance _log;

		private bool _dirty;

		private float _lastChange;

		public string Name => "BoneMenu";

		public bool Enabled => true;

		public void Initialize(Harmony harmony, Instance log)
		{
			_log = log;
			if (TypeExplorer.FindType("BoneLib.BoneMenu.Page") == null)
			{
				log.Warning("BoneLib not found: BoneMenu section skipped.");
				return;
			}
			try
			{
				Build();
			}
			catch (Exception ex)
			{
				log.Error("BoneMenu build failed: " + ex);
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private void Build()
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			Page val = Page.Root.CreatePage("HalfSwordControls", Color.cyan, 0, true);
			val.CreateFloat("Pos Sensitivity", Color.white, Mathf.Clamp(ModConfig.PosSensitivity.Value, 0.001f, 0.02f), 0.001f, 0.001f, 0.02f, (Action<float>)delegate(float v)
			{
				ModConfig.PosSensitivity.Value = Round(v);
				MarkDirty();
			});
			val.CreateFloat("Rot Sensitivity", Color.white, Mathf.Clamp(ModConfig.RotSensitivity.Value, 0.5f, 5f), 0.25f, 0.5f, 5f, (Action<float>)delegate(float v)
			{
				ModConfig.RotSensitivity.Value = Round(v);
				MarkDirty();
			});
			val.CreateBool("Invert Y", Color.white, ModConfig.InvertY.Value, (Action<bool>)delegate(bool v)
			{
				ModConfig.InvertY.Value = v;
				MarkDirty();
			});
			val.CreateFunction("Reset Hands", Color.yellow, (Action)delegate
			{
				FlatPlayerBridge.ResetHands();
				_log.Msg("Reset Hands pressed.");
			});
			_log.Msg("BoneMenu page 'HalfSwordControls' created.");
		}

		private static float Round(float v)
		{
			return Mathf.Round(v * 10000f) / 10000f;
		}

		private void MarkDirty()
		{
			_dirty = true;
			_lastChange = Time.realtimeSinceStartup;
		}

		public void OnUpdate()
		{
			if (_dirty && Time.realtimeSinceStartup - _lastChange > 1f)
			{
				Save();
			}
		}

		private void Save()
		{
			_dirty = false;
			MelonPreferences.Save();
		}

		public void Shutdown()
		{
			if (_dirty)
			{
				Save();
			}
		}
	}
}
namespace HalfSwordControls.Features.ApiProbe
{
	internal sealed class ApiProbeFeature : IFeature
	{
		private const string RigRootName = "RigManager(bonelab) [0]";

		private static readonly string[] Types = new string[17]
		{
			"Il2CppSLZ.Marrow.Hand", "Il2CppSLZ.Marrow.Grip", "Il2CppSLZ.Marrow.InteractableHost", "Il2CppSLZ.Marrow.ForcePullGrip", "Il2CppSLZ.Marrow.BaseController", "Il2CppSLZ.Marrow.OpenController", "Il2CppSLZ.Marrow.ControllerRig", "Il2CppSLZ.Marrow.OpenControllerRig", "Il2CppSLZ.Marrow.RigManager", "Il2CppSLZ.Marrow.PhysicsRig",
			"Il2CppSLZ.Marrow.RemapRig", "Il2CppSLZ.Marrow.PhysGrounder", "Il2CppSLZ.Marrow.Magazine", "Il2CppSLZ.Marrow.MagazineState", "Il2CppSLZ.Marrow.AmmoSocket", "Il2CppSLZ.Marrow.Gun", "Il2CppSLZ.Marrow.Blade"
		};

		private float _rigSeenAt = -1f;

		private bool _autoDumped;

		public string Name => "ApiProbe";

		public bool Enabled => false;

		public void Initialize(Harmony harmony, Instance log)
		{
		}

		public void OnUpdate()
		{
			bool flag = Input.GetKeyDown((KeyCode)290);
			if (!_autoDumped)
			{
				bool flag2 = (Object)(object)GameObject.Find("RigManager(bonelab) [0]") != (Object)null;
				if (flag2 && _rigSeenAt < 0f)
				{
					_rigSeenAt = Time.time;
				}
				if (flag2 && Time.time - _rigSeenAt > 10f)
				{
					_autoDumped = true;
					flag = true;
				}
			}
			if (flag)
			{
				Dump();
			}
		}

		private static void Dump()
		{
			try
			{
				StringBuilder stringBuilder = new StringBuilder();
				stringBuilder.AppendLine("######## BONELAB API DUMP ########");
				GameObject val = GameObject.Find("RigManager(bonelab) [0]");
				stringBuilder.AppendLine("######## HIERARCHY of RigManager(bonelab) [0] ########");
				if ((Object)(object)val != (Object)null)
				{
					int budget = 3000;
					DumpHierarchy(val.transform, 0, 6, stringBuilder, ref budget);
				}
				else
				{
					stringBuilder.AppendLine("(rig root not found)");
				}
				stringBuilder.AppendLine();
				TypeExplorer.DumpTypes(Types, stringBuilder);
				TypeExplorer.DumpAssembly("FlatPlayer", stringBuilder);
				string text = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "HalfSword_ApiDump.txt");
				File.WriteAllText(text, stringBuilder.ToString());
				MelonLogger.Msg("[ApiProbe] Dump written to " + text);
			}
			catch (Exception ex)
			{
				MelonLogger.Error("[ApiProbe] Dump failed: " + ex);
			}
		}

		private static void DumpHierarchy(Transform t, int depth, int maxDepth, StringBuilder sb, ref int budget)
		{
			if (budget-- <= 0)
			{
				return;
			}
			sb.AppendLine(new string(' ', depth * 2) + ((Object)t).name + (((Component)t).gameObject.activeSelf ? "" : "  (inactive)"));
			if (depth < maxDepth)
			{
				for (int i = 0; i < t.childCount; i++)
				{
					DumpHierarchy(t.GetChild(i), depth + 1, maxDepth, sb, ref budget);
				}
			}
		}

		public void Shutdown()
		{
		}
	}
}
namespace HalfSwordControls.Core
{
	internal static class FeatureRegistry
	{
		private static readonly List<IFeature> _all = new List<IFeature>();

		private static readonly List<IFeature> _active = new List<IFeature>();

		private static Instance _log;

		public static void Register(IFeature feature)
		{
			_all.Add(feature);
		}

		public static void InitializeAll(Harmony harmony, Instance log)
		{
			_log = log;
			foreach (IFeature item in _all)
			{
				if (!item.Enabled)
				{
					log.Msg("[" + item.Name + "] disabled in config.");
					continue;
				}
				try
				{
					item.Initialize(harmony, log);
					_active.Add(item);
					log.Msg("[" + item.Name + "] initialized.");
				}
				catch (Exception value)
				{
					log.Error($"[{item.Name}] failed to initialize: {value}");
				}
			}
		}

		public static void Update()
		{
			for (int i = 0; i < _active.Count; i++)
			{
				try
				{
					_active[i].OnUpdate();
				}
				catch (Exception value)
				{
					_log.Error($"[{_active[i].Name}] OnUpdate error, feature disabled: {value}");
					_active.RemoveAt(i);
					i--;
				}
			}
		}

		public static void LateUpdate()
		{
			for (int i = 0; i < _active.Count; i++)
			{
				try
				{
					_active[i].OnLateUpdate();
				}
				catch (Exception value)
				{
					_log.Error($"[{_active[i].Name}] OnLateUpdate error, feature disabled: {value}");
					_active.RemoveAt(i);
					i--;
				}
			}
		}

		public static void Shutdown()
		{
			foreach (IFeature item in _active)
			{
				try
				{
					item.Shutdown();
				}
				catch
				{
				}
			}
			_active.Clear();
		}
	}
	internal interface IFeature
	{
		string Name { get; }

		bool Enabled { get; }

		void Initialize(Harmony harmony, Instance log);

		void OnUpdate();

		void OnLateUpdate()
		{
		}

		void Shutdown();
	}
	internal static class ModConfig
	{
		private static MelonPreferences_Category _cat;

		public static MelonPreferences_Entry<bool> DecoupledInputEnabled;

		public static MelonPreferences_Entry<KeyCode> CameraKey;

		public static MelonPreferences_Entry<KeyCode> LeftHandButton;

		public static MelonPreferences_Entry<KeyCode> RightHandButton;

		public static MelonPreferences_Entry<KeyCode> RotateModifierKey;

		public static MelonPreferences_Entry<float> RotSensitivity;

		public static MelonPreferences_Entry<float> PosSensitivity;

		public static MelonPreferences_Entry<bool> InvertY;

		public static MelonPreferences_Entry<bool> RequireCursorLock;

		public static MelonPreferences_Entry<float> AimYawLimit;

		public static MelonPreferences_Entry<float> AimPitchLimit;

		public static MelonPreferences_Entry<float> AimXLimit;

		public static MelonPreferences_Entry<float> AimYLimit;

		public static MelonPreferences_Entry<float> ReachPerScroll;

		public static void Init()
		{
			_cat = MelonPreferences.CreateCategory("HalfSwordControls");
			DecoupledInputEnabled = _cat.CreateEntry<bool>("DecoupledInput_Enabled", true, "Feature 1: Decoupled Input", (string)null, false, false, (ValueValidator)null, (string)null);
			CameraKey = _cat.CreateEntry<KeyCode>("CameraKey", (KeyCode)308, "Hold: hand control off, camera free", (string)null, false, false, (ValueValidator)null, (string)null);
			LeftHandButton = _cat.CreateEntry<KeyCode>("LeftHandButton", (KeyCode)323, "Hold to control LEFT hand", (string)null, false, false, (ValueValidator)null, (string)null);
			RightHandButton = _cat.CreateEntry<KeyCode>("RightHandButton", (KeyCode)324, "Hold to control RIGHT hand", (string)null, false, false, (ValueValidator)null, (string)null);
			RotateModifierKey = _cat.CreateEntry<KeyCode>("RotateModifierKey", (KeyCode)304, "Hold with hand button: mouse rotates wrist instead of moving hand", (string)null, false, false, (ValueValidator)null, (string)null);
			RotSensitivity = _cat.CreateEntry<float>("RotSensitivity", 2f, "Wrist rotation (deg per mouse count)", (string)null, false, false, (ValueValidator)null, (string)null);
			PosSensitivity = _cat.CreateEntry<float>("PosSensitivity", 0.003f, "Hand movement (meters per mouse count)", (string)null, false, false, (ValueValidator)null, (string)null);
			InvertY = _cat.CreateEntry<bool>("InvertY", false, "Invert hand Y", (string)null, false, false, (ValueValidator)null, (string)null);
			RequireCursorLock = _cat.CreateEntry<bool>("RequireCursorLock", true, "Only control hands while cursor is locked", (string)null, false, false, (ValueValidator)null, (string)null);
			AimYawLimit = _cat.CreateEntry<float>("AimYawLimit", 120f, "Max wrist yaw from pose at press (deg, each side)", (string)null, false, false, (ValueValidator)null, (string)null);
			AimPitchLimit = _cat.CreateEntry<float>("AimPitchLimit", 85f, "Max wrist pitch from pose at press (deg, each side)", (string)null, false, false, (ValueValidator)null, (string)null);
			AimXLimit = _cat.CreateEntry<float>("AimXLimit", 0.5f, "Max hand X offset from pose at press (meters, each side)", (string)null, false, false, (ValueValidator)null, (string)null);
			AimYLimit = _cat.CreateEntry<float>("AimYLimit", 0.5f, "Max hand Y offset from pose at press (meters, each side)", (string)null, false, false, (ValueValidator)null, (string)null);
			ReachPerScroll = _cat.CreateEntry<float>("ReachPerScroll", 0.05f, "Thrust per scroll notch (meters)", (string)null, false, false, (ValueValidator)null, (string)null);
			MelonPreferences.Save();
		}
	}
	public sealed class ModEntry : MelonMod
	{
		public override void OnInitializeMelon()
		{
			ModConfig.Init();
			FeatureRegistry.Register(new DecoupledInputFeature());
			FeatureRegistry.Register(new ApiProbeFeature());
			FeatureRegistry.Register(new BoneMenuFeature());
			FeatureRegistry.InitializeAll(((MelonBase)this).HarmonyInstance, ((MelonBase)this).LoggerInstance);
			((MelonBase)this).LoggerInstance.Msg("HalfSwordControls 0.5.0 loaded (BoneMenu).");
		}

		public override void OnUpdate()
		{
			FeatureRegistry.Update();
		}

		public override void OnLateUpdate()
		{
			FeatureRegistry.LateUpdate();
		}

		public override void OnDeinitializeMelon()
		{
			FeatureRegistry.Shutdown();
		}
	}
}