Decompiled source of BONECAP v1.6.10

Mods/BoneCap.dll

Decompiled 6 hours ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading.Tasks;
using BONECAP;
using BONECAP.Capture;
using BONECAP.Export;
using BONECAP.Menu;
using BoneLib;
using BoneLib.BoneMenu;
using BoneLib.Notifications;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSLZ.Bonelab;
using Il2CppSLZ.Marrow;
using Il2CppSLZ.Marrow.SceneStreaming;
using Il2CppSLZ.Marrow.Warehouse;
using Il2CppSLZ.VRMK;
using Il2CppSystem;
using Il2CppTMPro;
using MelonLoader;
using MelonLoader.Preferences;
using MelonLoader.Utils;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.Rendering;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("BONECAP")]
[assembly: AssemblyDescription("BONECAP is a Motion capture CodeMod allowing you to export your avatar's motion into Blender")]
[assembly: AssemblyCompany("TheVrEnthusiast.BONECAP")]
[assembly: AssemblyProduct("BONECAP")]
[assembly: AssemblyFileVersion("1.6.10.0")]
[assembly: MelonInfo(typeof(Main), "BONECAP", "1.6.10", "TheVrEnthusiast", "https://thunderstore.io/c/bonelab/p/TheVrEnthusiast/BONECAP/")]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyVersion("1.6.10.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace BONECAP
{
	internal enum SaveFormat
	{
		Both,
		BoneCap,
		Bvh
	}
	public class Main : MelonMod
	{
		private sealed class SaveResult
		{
			public bool success;

			public string path;

			public int frames;

			public string error;
		}

		internal bool recording;

		internal bool usePlayerMarker = true;

		internal bool slowMoBypass;

		internal static SaveFormat saveFormat;

		internal static bool debugMode;

		internal const bool DebugToolsCompiled = false;

		internal bool fingerPhysicsActive;

		internal float frameRate = 60f;

		private float startTime;

		private float nextFrameTime;

		private float captureClock;

		private float lastPoseTime;

		internal bool bypassingSlowMo;

		private const int MaxCatchUpFrames = 8;

		internal AvatarSkeleton skeleton;

		internal CaptureSession session;

		internal IntPtr captureRigPointer = IntPtr.Zero;

		internal Transform sceneRoot;

		internal bool sceneRootAvailable;

		internal string avatarName = "Avatar";

		internal string lastSavedPath;

		internal MelonPreferences_Category prefs;

		internal MelonPreferences_Entry<float> prefFrameRate;

		internal MelonPreferences_Entry<bool> prefUsePlayerMarker;

		internal MelonPreferences_Entry<bool> prefSlowMoBypass;

		internal MelonPreferences_Entry<bool> prefDebugMode;

		private readonly ConcurrentQueue<SaveResult> saveResults = new ConcurrentQueue<SaveResult>();

		internal static Main Instance { get; private set; }

		internal static string CaptureDirectory => Path.Combine(MelonEnvironment.UserDataDirectory, "BoneCap");

		internal static string PoseDirectory => Path.Combine(CaptureDirectory, "Poses");

		public override void OnInitializeMelon()
		{
			Instance = this;
			InitPreferences();
			DebugLog("Initializing BONECAP.");
			Hooking.OnLevelLoaded += OnLevelLoaded;
			Hooking.OnLevelUnloaded += OnLevelUnloaded;
			BCmenu.Build(this);
			DebugLog("BoneMenu created.");
		}

		public override void OnUpdate()
		{
			SaveResult result;
			while (saveResults.TryDequeue(out result))
			{
				if (result.success)
				{
					lastSavedPath = result.path;
					Send("BoneCap", $"Capture Saved ({result.frames} frames)", (NotificationType)3, 3f);
				}
				else
				{
					Send("Error", "Capture failed to save", (NotificationType)2, 4f);
				}
			}
		}

		private void InitPreferences()
		{
			prefs = MelonPreferences.CreateCategory("BoneCap");
			prefFrameRate = prefs.CreateEntry<float>("FrameRate", 60f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			prefUsePlayerMarker = prefs.CreateEntry<bool>("UsePlayerMarker", true, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			prefSlowMoBypass = prefs.CreateEntry<bool>("SlowMoBypass", false, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			prefDebugMode = prefs.CreateEntry<bool>("DebugMode", false, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			frameRate = Mathf.Clamp(prefFrameRate.Value, 12f, 120f);
			usePlayerMarker = prefUsePlayerMarker.Value;
			slowMoBypass = prefSlowMoBypass.Value;
			debugMode = prefDebugMode.Value;
			Bind(prefFrameRate, delegate(float v)
			{
				frameRate = Mathf.Clamp(v, 12f, 120f);
			});
			Bind(prefUsePlayerMarker, delegate(bool v)
			{
				usePlayerMarker = v;
			});
			Bind(prefSlowMoBypass, delegate(bool v)
			{
				slowMoBypass = v;
			});
			Bind(prefDebugMode, delegate(bool v)
			{
				debugMode = v;
			});
		}

		private void Bind<T>(MelonPreferences_Entry<T> entry, Action<T> apply)
		{
			((MelonEventBase<LemonAction<T, T>>)(object)entry.OnEntryValueChanged).Subscribe((LemonAction<T, T>)delegate(T oldVal, T newVal)
			{
				apply(newVal);
				prefs.SaveToFile(false);
			}, 0, false);
		}

		private void OnLevelLoaded(LevelInfo level)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			DebugLog($"Level loaded: {level.title} ({level.barcode}).");
			skeleton = null;
			FindSceneRoot();
		}

		private void OnLevelUnloaded()
		{
			if (recording)
			{
				DebugLog("Level unloading while recording. Stopping and saving capture.");
				StopRecording();
			}
			PlayerMarkerPatches.Clear();
			sceneRoot = null;
			sceneRootAvailable = false;
			skeleton = null;
		}

		internal void FrameRateSetting(float fps)
		{
			if (recording)
			{
				Send("BoneCap", "Stop the capture before changing framerate", (NotificationType)1, 2f);
				return;
			}
			prefFrameRate.Value = fps;
			DebugLog($"Capture framerate set to {fps:0} FPS.");
			Send("BoneCap", $"Capture framerate: {fps:0} FPS", (NotificationType)0, 2f);
		}

		internal void SaveFormatSetting(SaveFormat format)
		{
			saveFormat = format;
			string text = format switch
			{
				SaveFormat.BoneCap => ".bonecap only", 
				SaveFormat.Both => ".bonecap + .bvh", 
				_ => ".bvh only", 
			};
			DebugLog("Save format set to " + text + ".");
			Send("BoneCap", "Saving: " + text, (NotificationType)0, 2f);
		}

		internal static void DebugLog(string message)
		{
			if (debugMode)
			{
				MelonLogger.Msg("[DEBUG] " + message);
			}
		}

		internal static void Send(string title, string message, NotificationType type, float length)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//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_0037: Expected O, but got Unknown
			Notifier.Send(new Notification
			{
				Title = NotificationText.op_Implicit(title),
				Message = NotificationText.op_Implicit(message),
				PopupLength = length,
				Type = type,
				ShowTitleOnPopup = true
			});
		}

		internal void FindSceneRoot()
		{
			DebugLog("Searching for Scene Root via cached PlayerMarker component.");
			sceneRoot = null;
			sceneRootAvailable = false;
			PlayerMarker instance = PlayerMarkerPatches.Instance;
			if ((Object)(object)instance != (Object)null && (Object)(object)((Component)instance).gameObject != (Object)null)
			{
				sceneRoot = ((Component)instance).transform;
				sceneRootAvailable = true;
				DebugLog("Scene Root found from cached PlayerMarker component: " + ((Object)((Component)instance).gameObject).name + ".");
				Send("BoneCap", "Scene Root found", (NotificationType)0, 2f);
				return;
			}
			DebugLog("PlayerMarker component not found. Trying GameObject name fallback.");
			GameObject val = GameObject.Find("PlayerMarker");
			if ((Object)(object)val != (Object)null)
			{
				sceneRoot = val.transform;
				sceneRootAvailable = true;
				DebugLog("Scene Root found from name fallback: " + ((Object)val).name + ".");
				Send("BoneCap", "Scene Root found", (NotificationType)0, 2f);
			}
			else
			{
				DebugLog("Scene Root not available. Recording will use start position fallback.");
				Send("BoneCap", "Recording With Scene Root not available, Root is set to current position.", (NotificationType)1, 4f);
			}
		}

		internal bool BuildSkeleton()
		{
			skeleton = AvatarSkeleton.Build(Player.RigManager);
			if (skeleton == null || !skeleton.IsValid)
			{
				skeleton = null;
				return false;
			}
			avatarName = skeleton.avatarName;
			return true;
		}

		internal void StartRecording()
		{
			//IL_0173: Unknown result type (might be due to invalid IL or missing references)
			//IL_0174: Unknown result type (might be due to invalid IL or missing references)
			//IL_0261: Unknown result type (might be due to invalid IL or missing references)
			if (recording)
			{
				Send("BoneCap", "Already capturing", (NotificationType)1, 2f);
				return;
			}
			DebugLog($"StartRecording requested. FPS={frameRate:0}, UsePlayerMarker={usePlayerMarker}.");
			RigManager rigManager = Player.RigManager;
			fingerPhysicsActive = FingerPhysicsFix.PhysicsFingers();
			if ((Object)(object)rigManager == (Object)null || (Object)(object)rigManager.physicsRig == (Object)null || (Object)(object)rigManager.physicsRig.artOutput == (Object)null || !BuildSkeleton())
			{
				DebugLog($"StartRecording failed. Rig={(((Object)(object)rigManager == (Object)null) ? "null" : ((Object)rigManager).name)}, Skeleton={((skeleton == null) ? "null" : skeleton.bones.Count.ToString())}.");
				Send("Error", "Rig or Avatar Not Found", (NotificationType)2, 4f);
				return;
			}
			ResolveOrigin(out var position, out var rotation, out var mode);
			session = new CaptureSession(skeleton, frameRate, mode, position, rotation, fingerPhysicsActive);
			captureRigPointer = ((Il2CppObjectBase)rigManager.physicsRig.artOutput).Pointer;
			RenderCaptureHook.Register();
			startTime = Time.unscaledTime;
			lastPoseTime = startTime;
			captureClock = 0f;
			bypassingSlowMo = false;
			nextFrameTime = 0f;
			recording = true;
			DebugLog($"Recording started. Avatar={avatarName}, Bones={skeleton.bones.Count}, FrameRate={frameRate:0}, Origin={mode} {position}.");
			Send("BoneCap", "Recording: " + avatarName, (NotificationType)0, 3f);
		}

		internal void ResolveOrigin(out Vector3 position, out Quaternion rotation, out string mode)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: 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_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: 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_011d: Unknown result type (might be due to invalid IL or missing references)
			if (usePlayerMarker && sceneRootAvailable && (Object)(object)sceneRoot != (Object)null)
			{
				position = sceneRoot.position;
				rotation = CaptureSession.Flatten(sceneRoot.rotation);
				mode = "PlayerMarker";
				return;
			}
			if (usePlayerMarker)
			{
				DebugLog("Scene Root requested but unavailable. Falling back to start position.");
			}
			float num = float.MaxValue;
			foreach (CapturedBone bone in skeleton.bones)
			{
				num = Mathf.Min(num, bone.transform.position.y);
			}
			Vector3 position2 = skeleton.bones[skeleton.HipsIndex].transform.position;
			Transform head = Player.Head;
			position = new Vector3(position2.x, num, position2.z);
			rotation = CaptureSession.Flatten(((Object)(object)head != (Object)null) ? head.rotation : skeleton.root.rotation);
			mode = "Start";
		}

		internal void OnFrameRendered()
		{
			session?.RefreshSoftBones();
		}

		internal void OnArtRigPosed()
		{
			if (session == null)
			{
				return;
			}
			Avatar avatar = Player.Avatar;
			if ((Object)(object)avatar == (Object)null || (Object)(object)skeleton.avatar == (Object)null || ((Il2CppObjectBase)avatar).Pointer != ((Il2CppObjectBase)skeleton.avatar).Pointer)
			{
				DebugLog("Avatar changed while recording. Stopping and saving capture.");
				StopRecording();
				return;
			}
			float num = AdvanceClock();
			if (num < nextFrameTime)
			{
				return;
			}
			if (!session.Capture())
			{
				DebugLog("A tracked bone was destroyed. Stopping and saving capture.");
				StopRecording();
				return;
			}
			float num2 = 1f / frameRate;
			nextFrameTime += num2;
			for (int i = 0; i < 8; i++)
			{
				if (!(num >= nextFrameTime))
				{
					break;
				}
				session.RepeatLastFrame();
				nextFrameTime += num2;
			}
			if (num >= nextFrameTime)
			{
				nextFrameTime = num + num2;
			}
		}

		private float AdvanceClock()
		{
			float unscaledTime = Time.unscaledTime;
			float num = unscaledTime - lastPoseTime;
			lastPoseTime = unscaledTime;
			float timeScale = Time.timeScale;
			bool flag = slowMoBypass && timeScale > 0f && timeScale < 0.999f;
			if (flag != bypassingSlowMo)
			{
				bypassingSlowMo = flag;
				DebugLog(flag ? $"SlowMo detected ({timeScale:0.##}x). Skipping frames so it plays full speed." : "SlowMo ended. Back to normal capture.");
			}
			captureClock += (flag ? (num * timeScale) : num);
			return captureClock;
		}

		internal void StopRecording()
		{
			if (!recording)
			{
				DebugLog("StopRecording ignored because recording is false.");
				return;
			}
			recording = false;
			captureRigPointer = IntPtr.Zero;
			CaptureSession captureSession = session;
			session = null;
			if (captureSession == null || captureSession.FrameCount == 0)
			{
				DebugLog("StopRecording: no frames were captured.");
				Send("BoneCap", "Nothing was captured", (NotificationType)1, 3f);
				return;
			}
			DebugLog($"Stopping recording. Avatar={avatarName}, Frames={captureSession.FrameCount}.");
			MotionClip clip;
			try
			{
				clip = captureSession.ToClip();
			}
			catch (Exception value)
			{
				MelonLogger.Error($"Failed to build capture: {value}");
				Send("Error", "Capture failed to save", (NotificationType)2, 4f);
				return;
			}
			int frames = captureSession.FrameCount;
			SaveFormat format = saveFormat;
			string directory = CaptureDirectory;
			string suffix = $"{DateTime.Now:yyyyMMdd_HHmmss}";
			Send("BoneCap", "Saving capture...", (NotificationType)0, 2f);
			Task.Run(delegate
			{
				try
				{
					string path = SaveCapture(clip, directory, suffix, format);
					saveResults.Enqueue(new SaveResult
					{
						success = true,
						path = path,
						frames = frames
					});
				}
				catch (Exception ex)
				{
					MelonLogger.Error($"Failed to save capture: {ex}");
					saveResults.Enqueue(new SaveResult
					{
						success = false,
						error = ex.Message
					});
				}
			});
		}

		internal static string SaveCapture(MotionClip clip, string directory, string suffix, SaveFormat? formatOverride = null)
		{
			SaveFormat saveFormat = formatOverride ?? Main.saveFormat;
			Directory.CreateDirectory(directory);
			string text = Path.Combine(directory, clip.SafeName + "_" + suffix);
			string text2 = text + ".bonecap";
			string text3 = text + ".bvh";
			if (saveFormat == SaveFormat.Bvh)
			{
				BvhWriter.Write(text3, clip);
				DebugLog("BVH saved to " + text3 + ".");
				return text3;
			}
			BoneCapWriter.Write(text2, clip);
			DebugLog("BoneCap saved to " + text2 + ".");
			if (saveFormat == SaveFormat.Both)
			{
				try
				{
					BvhWriter.Write(text3, clip);
					DebugLog("BVH saved to " + text3 + ".");
				}
				catch (Exception ex)
				{
					MelonLogger.Warning("BVH export failed, .bonecap is still saved: " + ex.Message);
				}
			}
			return text2;
		}
	}
	internal static class FingerPhysicsFix
	{
		public static bool Installed()
		{
			foreach (MelonMod registeredMelon in MelonTypeBase<MelonMod>.RegisteredMelons)
			{
				if (((object)registeredMelon).GetType().Assembly.GetName().Name.Equals("FingerPhysics", StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			return false;
		}

		public static bool PhysicsFingers()
		{
			Main.DebugLog("Checking for FingerPhysics...");
			if (!Installed())
			{
				Main.DebugLog("FingerPhysics is not installed.");
				return false;
			}
			MelonPreferences_Category category = MelonPreferences.GetCategory("FingerPhysics");
			MelonPreferences_Entry<bool> val = ((category != null) ? category.GetEntry<bool>("Enabled") : null);
			if (val == null)
			{
				Main.DebugLog("FingerPhysics 'Enabled' preference not found.");
				return false;
			}
			Main.DebugLog($"FingerPhysics Enabled: {val.Value}");
			return val.Value;
		}
	}
	internal static class RagdollPlayerFix
	{
		public static bool Installed()
		{
			foreach (MelonMod registeredMelon in MelonTypeBase<MelonMod>.RegisteredMelons)
			{
				if (((object)registeredMelon).GetType().Assembly.GetName().Name.Equals("RagdollPlayer", StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			return false;
		}

		public static bool Enabled()
		{
			if (!Installed())
			{
				return false;
			}
			MelonPreferences_Category category = MelonPreferences.GetCategory("Ragdoll Player");
			return ((category != null) ? category.GetEntry<bool>("IsEnabled") : null)?.Value ?? false;
		}

		public static bool Ragdolled()
		{
			try
			{
				PhysicsRig physicsRig = Player.PhysicsRig;
				if ((Object)(object)physicsRig == (Object)null)
				{
					return false;
				}
				return physicsRig.torso.shutdown || !physicsRig.ballLocoEnabled;
			}
			catch
			{
				return false;
			}
		}

		public static void LogStatus()
		{
			Main.DebugLog(Installed() ? $"RagdollPlayer found. Enabled: {Enabled()}" : "RagdollPlayer is not installed.");
		}
	}
	internal static class BuildInfo
	{
		public const string Name = "BONECAP";

		public const string Description = "BONECAP is a Motion capture CodeMod allowing you to export your avatar's motion into Blender";

		public const string Author = "TheVrEnthusiast";

		public const string Company = "TheVrEnthusiast.BONECAP";

		public const string Version = "1.6.10";

		public const string DownloadLink = "https://thunderstore.io/c/bonelab/p/TheVrEnthusiast/BONECAP/";
	}
}
namespace BONECAP.Export
{
	internal static class BoneCapWriter
	{
		public const string Magic = "BONECAP1";

		public static void Write(string path, MotionClip clip)
		{
			byte[] bytes = Encoding.UTF8.GetBytes(BuildHeader(clip));
			using FileStream output = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, 65536);
			using BinaryWriter binaryWriter = new BinaryWriter(output);
			binaryWriter.Write(Encoding.ASCII.GetBytes("BONECAP1"));
			binaryWriter.Write(bytes.Length);
			binaryWriter.Write(bytes);
			binaryWriter.Write(clip.frameCount);
			binaryWriter.Write(clip.BoneCount);
			int num = clip.frameCount * clip.BoneCount * 7;
			for (int i = 0; i < num; i++)
			{
				binaryWriter.Write(clip.frames[i]);
			}
		}

		private static string BuildHeader(MotionClip clip)
		{
			StringBuilder stringBuilder = new StringBuilder(4096);
			stringBuilder.Append('{');
			Pair(stringBuilder, "format", "bonecap");
			stringBuilder.Append(',');
			stringBuilder.Append("\"formatVersion\":1,");
			Pair(stringBuilder, "modVersion", clip.version);
			stringBuilder.Append(',');
			Pair(stringBuilder, "avatar", clip.avatarName);
			stringBuilder.Append(',');
			Pair(stringBuilder, "barcode", clip.barcode);
			stringBuilder.Append(',');
			Pair(stringBuilder, "origin", clip.originMode);
			stringBuilder.Append(',');
			Pair(stringBuilder, "space", "unity");
			stringBuilder.Append(',');
			stringBuilder.Append("\"fps\":").Append(F(clip.frameRate)).Append(',');
			stringBuilder.Append("\"frames\":").Append(clip.frameCount).Append(',');
			stringBuilder.Append("\"scale\":").Append(F(clip.scale)).Append(',');
			stringBuilder.Append("\"hips\":").Append(clip.hipsIndex).Append(',');
			stringBuilder.Append("\"fingerPhysics\":").Append(clip.fingerPhysics ? "true" : "false").Append(',');
			stringBuilder.Append("\"bones\":[");
			for (int i = 0; i < clip.BoneCount; i++)
			{
				if (i > 0)
				{
					stringBuilder.Append(',');
				}
				stringBuilder.Append('{');
				Pair(stringBuilder, "name", clip.names[i]);
				stringBuilder.Append(',');
				Pair(stringBuilder, "human", clip.human[i]);
				stringBuilder.Append(',');
				stringBuilder.Append("\"parent\":").Append(clip.parents[i]).Append(',');
				stringBuilder.Append("\"bind\":").Append(clip.bindPose[i] ? "true" : "false").Append(',');
				stringBuilder.Append("\"rest\":[");
				for (int j = 0; j < 7; j++)
				{
					if (j > 0)
					{
						stringBuilder.Append(',');
					}
					stringBuilder.Append(F(clip.rest[i * 7 + j]));
				}
				stringBuilder.Append("]}");
			}
			stringBuilder.Append("]}");
			return stringBuilder.ToString();
		}

		private static void Pair(StringBuilder sb, string key, string value)
		{
			sb.Append('"').Append(key).Append("\":\"");
			string text = value ?? "";
			foreach (char c in text)
			{
				switch (c)
				{
				case '"':
					sb.Append("\\\"");
					continue;
				case '\\':
					sb.Append("\\\\");
					continue;
				case '\n':
					sb.Append("\\n");
					continue;
				case '\r':
					sb.Append("\\r");
					continue;
				case '\t':
					sb.Append("\\t");
					continue;
				}
				if (c < ' ')
				{
					StringBuilder stringBuilder = sb.Append("\\u");
					int num = c;
					stringBuilder.Append(num.ToString("x4"));
				}
				else
				{
					sb.Append(c);
				}
			}
			sb.Append('"');
		}

		private static string F(float v)
		{
			return v.ToString("R", CultureInfo.InvariantCulture);
		}
	}
	internal static class BvhWriter
	{
		private const string VirtualRoot = "BoneCapRoot";

		public static void Write(string path, MotionClip clip)
		{
			int boneCount = clip.BoneCount;
			List<int>[] array = new List<int>[boneCount];
			List<int> list = new List<int>();
			for (int i = 0; i < boneCount; i++)
			{
				array[i] = new List<int>();
			}
			for (int j = 0; j < boneCount; j++)
			{
				if (clip.parents[j] >= 0)
				{
					array[clip.parents[j]].Add(j);
				}
				else
				{
					list.Add(j);
				}
			}
			bool flag = list.Count != 1;
			bool[] array2 = new bool[boneCount];
			for (int k = 0; k < boneCount; k++)
			{
				array2[k] = clip.parents[k] < 0 || k == clip.hipsIndex;
			}
			Vec[] array3 = new Vec[boneCount];
			Quat[] array4 = new Quat[boneCount];
			for (int l = 0; l < boneCount; l++)
			{
				array3[l] = clip.RestPosition(l).Mirrored;
				array4[l] = clip.RestRotation(l).Mirrored;
			}
			List<int> list2 = new List<int>(boneCount);
			StringBuilder stringBuilder = new StringBuilder(65536);
			stringBuilder.AppendLine("HIERARCHY");
			if (flag)
			{
				stringBuilder.AppendLine("ROOT BoneCapRoot");
				stringBuilder.AppendLine("{");
				stringBuilder.AppendLine("    OFFSET 0.000000 0.000000 0.000000");
				stringBuilder.AppendLine("    CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation");
				foreach (int item in list)
				{
					WriteJoint(stringBuilder, clip, item, 1, array, array2, array3, list2, isRoot: false);
				}
				stringBuilder.AppendLine("}");
			}
			else
			{
				WriteJoint(stringBuilder, clip, list[0], 0, array, array2, array3, list2, isRoot: true);
			}
			stringBuilder.AppendLine("MOTION");
			StringBuilder stringBuilder2 = stringBuilder;
			StringBuilder stringBuilder3 = stringBuilder2;
			StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(8, 1, stringBuilder2);
			handler.AppendLiteral("Frames: ");
			handler.AppendFormatted(clip.frameCount);
			stringBuilder3.AppendLine(ref handler);
			stringBuilder2 = stringBuilder;
			StringBuilder stringBuilder4 = stringBuilder2;
			handler = new StringBuilder.AppendInterpolatedStringHandler(12, 1, stringBuilder2);
			handler.AppendLiteral("Frame Time: ");
			handler.AppendFormatted(F(1.0 / (double)clip.frameRate));
			stringBuilder4.AppendLine(ref handler);
			Vec[] array5 = new Vec[boneCount];
			Quat[] array6 = new Quat[boneCount];
			using StreamWriter streamWriter = new StreamWriter(path, append: false, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
			streamWriter.Write(stringBuilder.ToString());
			StringBuilder stringBuilder5 = new StringBuilder(boneCount * 64);
			for (int m = 0; m < clip.frameCount; m++)
			{
				for (int n = 0; n < boneCount; n++)
				{
					array5[n] = clip.Position(m, n).Mirrored;
					array6[n] = (clip.Rotation(m, n).Mirrored * array4[n].Conjugate).Normalized();
				}
				stringBuilder5.Clear();
				if (flag)
				{
					stringBuilder5.Append("0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 ");
				}
				foreach (int item2 in list2)
				{
					int num = clip.parents[item2];
					Quat quat = ((num >= 0) ? array6[num] : Quat.Identity);
					if (array2[item2])
					{
						Vec vec = ((num >= 0) ? quat.Conjugate.Rotate(array5[item2] - array5[num]) : array5[item2]);
						stringBuilder5.Append(F(vec.x)).Append(' ').Append(F(vec.y))
							.Append(' ')
							.Append(F(vec.z))
							.Append(' ');
					}
					Vec vec2 = (quat.Conjugate * array6[item2]).Normalized().ToEulerZXY();
					stringBuilder5.Append(F(vec2.z)).Append(' ').Append(F(vec2.x))
						.Append(' ')
						.Append(F(vec2.y))
						.Append(' ');
				}
				streamWriter.WriteLine(stringBuilder5.ToString().TrimEnd());
			}
		}

		private static void WriteJoint(StringBuilder sb, MotionClip clip, int i, int depth, List<int>[] children, bool[] hasPosition, Vec[] restPos, List<int> order, bool isRoot)
		{
			string text = new string(' ', depth * 4);
			int num = clip.parents[i];
			Vec vec = ((num >= 0) ? (restPos[i] - restPos[num]) : restPos[i]);
			order.Add(i);
			StringBuilder stringBuilder = sb;
			StringBuilder stringBuilder2 = stringBuilder;
			StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(1, 3, stringBuilder);
			handler.AppendFormatted(text);
			handler.AppendFormatted(isRoot ? "ROOT" : "JOINT");
			handler.AppendLiteral(" ");
			handler.AppendFormatted(Sanitize(clip.names[i]));
			stringBuilder2.AppendLine(ref handler);
			stringBuilder = sb;
			StringBuilder stringBuilder3 = stringBuilder;
			handler = new StringBuilder.AppendInterpolatedStringHandler(1, 1, stringBuilder);
			handler.AppendFormatted(text);
			handler.AppendLiteral("{");
			stringBuilder3.AppendLine(ref handler);
			stringBuilder = sb;
			StringBuilder stringBuilder4 = stringBuilder;
			handler = new StringBuilder.AppendInterpolatedStringHandler(13, 4, stringBuilder);
			handler.AppendFormatted(text);
			handler.AppendLiteral("    OFFSET ");
			handler.AppendFormatted(F(vec.x));
			handler.AppendLiteral(" ");
			handler.AppendFormatted(F(vec.y));
			handler.AppendLiteral(" ");
			handler.AppendFormatted(F(vec.z));
			stringBuilder4.AppendLine(ref handler);
			sb.AppendLine(hasPosition[i] ? (text + "    CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation") : (text + "    CHANNELS 3 Zrotation Xrotation Yrotation"));
			if (children[i].Count == 0)
			{
				Vec vec2 = ((num >= 0) ? (restPos[i] - restPos[num]) : new Vec(0.0, 1.0, 0.0));
				double length = vec2.Length;
				Vec vec3 = ((length > 1E-06) ? (vec2 * (Math.Max(0.02, length * 0.5) / length)) : new Vec(0.0, 0.05, 0.0));
				stringBuilder = sb;
				StringBuilder stringBuilder5 = stringBuilder;
				handler = new StringBuilder.AppendInterpolatedStringHandler(12, 1, stringBuilder);
				handler.AppendFormatted(text);
				handler.AppendLiteral("    End Site");
				stringBuilder5.AppendLine(ref handler);
				stringBuilder = sb;
				StringBuilder stringBuilder6 = stringBuilder;
				handler = new StringBuilder.AppendInterpolatedStringHandler(5, 1, stringBuilder);
				handler.AppendFormatted(text);
				handler.AppendLiteral("    {");
				stringBuilder6.AppendLine(ref handler);
				stringBuilder = sb;
				StringBuilder stringBuilder7 = stringBuilder;
				handler = new StringBuilder.AppendInterpolatedStringHandler(17, 4, stringBuilder);
				handler.AppendFormatted(text);
				handler.AppendLiteral("        OFFSET ");
				handler.AppendFormatted(F(vec3.x));
				handler.AppendLiteral(" ");
				handler.AppendFormatted(F(vec3.y));
				handler.AppendLiteral(" ");
				handler.AppendFormatted(F(vec3.z));
				stringBuilder7.AppendLine(ref handler);
				stringBuilder = sb;
				StringBuilder stringBuilder8 = stringBuilder;
				handler = new StringBuilder.AppendInterpolatedStringHandler(5, 1, stringBuilder);
				handler.AppendFormatted(text);
				handler.AppendLiteral("    }");
				stringBuilder8.AppendLine(ref handler);
			}
			else
			{
				foreach (int item in children[i])
				{
					WriteJoint(sb, clip, item, depth + 1, children, hasPosition, restPos, order, isRoot: false);
				}
			}
			stringBuilder = sb;
			StringBuilder stringBuilder9 = stringBuilder;
			handler = new StringBuilder.AppendInterpolatedStringHandler(1, 1, stringBuilder);
			handler.AppendFormatted(text);
			handler.AppendLiteral("}");
			stringBuilder9.AppendLine(ref handler);
		}

		private static string Sanitize(string name)
		{
			return name.Replace(' ', '_');
		}

		private static string F(double v)
		{
			return v.ToString("0.000000", CultureInfo.InvariantCulture);
		}
	}
	internal sealed class MotionClip
	{
		public const int Stride = 7;

		public string avatarName = "Avatar";

		public string barcode = "";

		public string originMode = "Start";

		public string version = "1.6.10";

		public float frameRate = 60f;

		public float scale = 1f;

		public bool fingerPhysics;

		public int hipsIndex;

		public string[] names = Array.Empty<string>();

		public string[] human = Array.Empty<string>();

		public int[] parents = Array.Empty<int>();

		public bool[] bindPose = Array.Empty<bool>();

		public float[] rest = Array.Empty<float>();

		public List<float> frames = new List<float>();

		public int frameCount;

		public int BoneCount => names.Length;

		public string SafeName
		{
			get
			{
				string text = (string.IsNullOrWhiteSpace(avatarName) ? "Avatar" : avatarName);
				char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
				foreach (char oldChar in invalidFileNameChars)
				{
					text = text.Replace(oldChar, '_');
				}
				return text.Replace(' ', '_');
			}
		}

		public Vec Position(int frame, int bone)
		{
			int num = (frame * names.Length + bone) * 7;
			return new Vec(frames[num], frames[num + 1], frames[num + 2]);
		}

		public Quat Rotation(int frame, int bone)
		{
			int num = (frame * names.Length + bone) * 7;
			return new Quat(frames[num + 3], frames[num + 4], frames[num + 5], frames[num + 6]).Normalized();
		}

		public Vec RestPosition(int bone)
		{
			int num = bone * 7;
			return new Vec(rest[num], rest[num + 1], rest[num + 2]);
		}

		public Quat RestRotation(int bone)
		{
			int num = bone * 7;
			return new Quat(rest[num + 3], rest[num + 4], rest[num + 5], rest[num + 6]).Normalized();
		}
	}
	internal readonly struct Vec
	{
		public readonly double x;

		public readonly double y;

		public readonly double z;

		public double Length => Math.Sqrt(x * x + y * y + z * z);

		public Vec Mirrored => new Vec(0.0 - x, y, z);

		public Vec(double x, double y, double z)
		{
			this.x = x;
			this.y = y;
			this.z = z;
		}

		public static Vec operator +(Vec a, Vec b)
		{
			return new Vec(a.x + b.x, a.y + b.y, a.z + b.z);
		}

		public static Vec operator -(Vec a, Vec b)
		{
			return new Vec(a.x - b.x, a.y - b.y, a.z - b.z);
		}

		public static Vec operator *(Vec a, double s)
		{
			return new Vec(a.x * s, a.y * s, a.z * s);
		}
	}
	internal readonly struct Quat
	{
		public readonly double x;

		public readonly double y;

		public readonly double z;

		public readonly double w;

		public static readonly Quat Identity = new Quat(0.0, 0.0, 0.0, 1.0);

		public Quat Conjugate => new Quat(0.0 - x, 0.0 - y, 0.0 - z, w);

		public Quat Mirrored => new Quat(x, 0.0 - y, 0.0 - z, w);

		public Quat(double x, double y, double z, double w)
		{
			this.x = x;
			this.y = y;
			this.z = z;
			this.w = w;
		}

		public Quat Normalized()
		{
			double num = Math.Sqrt(x * x + y * y + z * z + w * w);
			if (!(num < 1E-12))
			{
				return new Quat(x / num, y / num, z / num, w / num);
			}
			return Identity;
		}

		public static Quat operator *(Quat a, Quat b)
		{
			return new Quat(a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y, a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x, a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w, a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z);
		}

		public Vec Rotate(Vec v)
		{
			double num = 2.0 * (y * v.z - z * v.y);
			double num2 = 2.0 * (z * v.x - x * v.z);
			double num3 = 2.0 * (x * v.y - y * v.x);
			return new Vec(v.x + w * num + (y * num3 - z * num2), v.y + w * num2 + (z * num - x * num3), v.z + w * num3 + (x * num2 - y * num));
		}

		public Vec ToEulerZXY()
		{
			double num = 2.0 * (x * y - w * z);
			double num2 = 1.0 - 2.0 * (x * x + z * z);
			double num3 = 2.0 * (x * z - w * y);
			double num4 = 2.0 * (y * z + w * x);
			double num5 = 1.0 - 2.0 * (x * x + y * y);
			double num6 = Math.Asin(Math.Max(-1.0, Math.Min(1.0, num4)));
			double num7;
			double num8;
			if (Math.Abs(num4) < 0.9999999)
			{
				num7 = Math.Atan2(0.0 - num3, num5);
				num8 = Math.Atan2(0.0 - num, num2);
			}
			else
			{
				double num9 = 1.0 - 2.0 * (y * y + z * z);
				double num10 = 2.0 * (x * y + w * z);
				num7 = 0.0;
				num8 = Math.Atan2(num10, num9);
			}
			return new Vec(num6 * (180.0 / Math.PI), num7 * (180.0 / Math.PI), num8 * (180.0 / Math.PI));
		}
	}
}
namespace BONECAP.Capture
{
	internal sealed class CapturedBone
	{
		public Transform transform;

		public string name;

		public string human;

		public int parent;

		public bool bindPose;

		public Vector3 restPosition;

		public Quaternion restRotation;
	}
	internal sealed class AvatarSkeleton
	{
		public Avatar avatar;

		public Transform root;

		public string avatarName = "Avatar";

		public string barcode = "";

		public float scale = 1f;

		public int skinnedRenderers;

		public int boundBones;

		public readonly List<CapturedBone> bones = new List<CapturedBone>();

		private readonly List<Matrix4x4> restMatrices = new List<Matrix4x4>();

		private readonly HashSet<string> usedNames = new HashSet<string>();

		public bool IsValid
		{
			get
			{
				if ((Object)(object)avatar != (Object)null && (Object)(object)root != (Object)null)
				{
					return bones.Count > 0;
				}
				return false;
			}
		}

		public int HipsIndex
		{
			get
			{
				for (int i = 0; i < bones.Count; i++)
				{
					if (bones[i].human == "Hips")
					{
						return i;
					}
				}
				return 0;
			}
		}

		public static AvatarSkeleton Build(RigManager rig)
		{
			//IL_0062: 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_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_0227: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: 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_0128: Unknown result type (might be due to invalid IL or missing references)
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0191: Unknown result type (might be due to invalid IL or missing references)
			//IL_0196: Unknown result type (might be due to invalid IL or missing references)
			//IL_019a: Unknown result type (might be due to invalid IL or missing references)
			//IL_019f: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)rig == (Object)null || (Object)(object)rig.avatar == (Object)null)
			{
				Main.DebugLog("AvatarSkeleton: RigManager or avatar is null.");
				return null;
			}
			Avatar val = rig.avatar;
			AvatarSkeleton avatarSkeleton = new AvatarSkeleton
			{
				avatar = val,
				root = ((Component)val).transform,
				avatarName = LittleSaintNameIsland(rig, val),
				barcode = GetThatDiddyBarCode(rig),
				scale = Mathf.Max(Mathf.Abs(((Component)val).transform.lossyScale.y), 0.0001f)
			};
			int instanceID = ((Object)avatarSkeleton.root).GetInstanceID();
			Matrix4x4 worldToLocalMatrix = avatarSkeleton.root.worldToLocalMatrix;
			Dictionary<int, Transform> dictionary = new Dictionary<int, Transform>();
			Dictionary<int, Matrix4x4> dictionary2 = new Dictionary<int, Matrix4x4>();
			Dictionary<int, string> dictionary3 = new Dictionary<int, string>();
			foreach (SkinnedMeshRenderer componentsInChild in ((Component)val).GetComponentsInChildren<SkinnedMeshRenderer>(true))
			{
				if ((Object)(object)componentsInChild == (Object)null)
				{
					continue;
				}
				Mesh sharedMesh = componentsInChild.sharedMesh;
				if ((Object)(object)sharedMesh == (Object)null)
				{
					continue;
				}
				Il2CppReferenceArray<Transform> val2 = componentsInChild.bones;
				Il2CppStructArray<Matrix4x4> bindposes = sharedMesh.bindposes;
				if (val2 == null || bindposes == null)
				{
					continue;
				}
				avatarSkeleton.skinnedRenderers++;
				Matrix4x4 val3 = worldToLocalMatrix * ((Component)componentsInChild).transform.localToWorldMatrix;
				int num = Math.Min(((Il2CppArrayBase<Transform>)(object)val2).Length, ((Il2CppArrayBase<Matrix4x4>)(object)bindposes).Length);
				for (int i = 0; i < num; i++)
				{
					Transform val4 = ((Il2CppArrayBase<Transform>)(object)val2)[i];
					if (!((Object)(object)val4 == (Object)null) && IsUnder(val4, instanceID))
					{
						int instanceID2 = ((Object)val4).GetInstanceID();
						dictionary[instanceID2] = val4;
						if (!dictionary2.ContainsKey(instanceID2))
						{
							Matrix4x4 val5 = ((Il2CppArrayBase<Matrix4x4>)(object)bindposes)[i];
							dictionary2[instanceID2] = val3 * ((Matrix4x4)(ref val5)).inverse;
						}
					}
				}
			}
			Animator animator = val.animator;
			if ((Object)(object)animator != (Object)null)
			{
				for (int j = 0; j < 55; j++)
				{
					Transform val6 = null;
					try
					{
						val6 = animator.GetBoneTransform((HumanBodyBones)j);
					}
					catch
					{
					}
					if (!((Object)(object)val6 == (Object)null) && IsUnder(val6, instanceID))
					{
						int instanceID3 = ((Object)val6).GetInstanceID();
						dictionary[instanceID3] = val6;
						dictionary3[instanceID3] = ((object)(HumanBodyBones)j/*cast due to .constrained prefix*/).ToString();
					}
				}
			}
			foreach (Transform item in new List<Transform>(dictionary.Values))
			{
				Transform parent = item.parent;
				while ((Object)(object)parent != (Object)null && ((Object)parent).GetInstanceID() != instanceID)
				{
					dictionary[((Object)parent).GetInstanceID()] = parent;
					parent = parent.parent;
				}
			}
			avatarSkeleton.Collect(avatarSkeleton.root, -1, worldToLocalMatrix, dictionary, dictionary2, dictionary3);
			avatarSkeleton.restMatrices.Clear();
			avatarSkeleton.usedNames.Clear();
			Main.DebugLog($"AvatarSkeleton built. Avatar={avatarSkeleton.avatarName}, Bones={avatarSkeleton.bones.Count}, Bindposed={avatarSkeleton.boundBones}, Renderers={avatarSkeleton.skinnedRenderers}, Scale={avatarSkeleton.scale:F4}.");
			return avatarSkeleton;
		}

		private void Collect(Transform parentTransform, int parentIndex, Matrix4x4 worldToModel, Dictionary<int, Transform> bonesToCapture, Dictionary<int, Matrix4x4> boneBindPoses, Dictionary<int, string> boneHumanNames)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: 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_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: 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_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: 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_011d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0132: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: 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)
			//IL_0074: 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_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			for (int i = 0; i < parentTransform.childCount; i++)
			{
				Transform child = parentTransform.GetChild(i);
				int instanceID = ((Object)child).GetInstanceID();
				if (!bonesToCapture.ContainsKey(instanceID))
				{
					Collect(child, parentIndex, worldToModel, bonesToCapture, boneBindPoses, boneHumanNames);
					continue;
				}
				Matrix4x4 value;
				bool flag = boneBindPoses.TryGetValue(instanceID, out value);
				if (!flag)
				{
					value = ((parentIndex >= 0) ? restMatrices[parentIndex] : (worldToModel * child.parent.localToWorldMatrix)) * Matrix4x4.TRS(child.localPosition, child.localRotation, child.localScale);
				}
				boneHumanNames.TryGetValue(instanceID, out var value2);
				CapturedBone item = new CapturedBone
				{
					transform = child,
					name = UniqueName(((Object)child).name),
					human = (value2 ?? ""),
					parent = parentIndex,
					bindPose = flag,
					restPosition = new Vector3(value.m03, value.m13, value.m23),
					restRotation = MatrixRotation(value)
				};
				if (flag)
				{
					boundBones++;
				}
				bones.Add(item);
				restMatrices.Add(value);
				Collect(child, bones.Count - 1, worldToModel, bonesToCapture, boneBindPoses, boneHumanNames);
			}
		}

		private string UniqueName(string name)
		{
			string text = name;
			int num = 2;
			while (!usedNames.Add(text))
			{
				text = $"{name}_{num++}";
			}
			return text;
		}

		private static bool IsUnder(Transform transform, int rootId)
		{
			Transform val = transform;
			while ((Object)(object)val != (Object)null)
			{
				if (((Object)val).GetInstanceID() == rootId)
				{
					return true;
				}
				val = val.parent;
			}
			return false;
		}

		internal static Quaternion MatrixRotation(Matrix4x4 m)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: 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_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = default(Vector3);
			((Vector3)(ref val))..ctor(m.m02, m.m12, m.m22);
			Vector3 val2 = default(Vector3);
			((Vector3)(ref val2))..ctor(m.m01, m.m11, m.m21);
			if (((Vector3)(ref val)).sqrMagnitude < 1E-12f || ((Vector3)(ref val2)).sqrMagnitude < 1E-12f)
			{
				return Quaternion.identity;
			}
			return Quaternion.LookRotation(val, val2);
		}

		private static string LittleSaintNameIsland(RigManager rig, Avatar avatar)
		{
			try
			{
				AvatarCrate val = ((CrateReferenceT<AvatarCrate>)(object)rig.AvatarCrate)?.Crate;
				if ((Object)(object)val != (Object)null && !string.IsNullOrWhiteSpace(((Scannable)val).Title))
				{
					return ((Scannable)val).Title;
				}
			}
			catch
			{
			}
			return ((Object)((Component)avatar).gameObject).name.Replace("(Clone)", "").Trim();
		}

		private static string GetThatDiddyBarCode(RigManager rig)
		{
			try
			{
				AvatarCrateReference avatarCrate = rig.AvatarCrate;
				object obj;
				if (avatarCrate == null)
				{
					obj = null;
				}
				else
				{
					Barcode obj2 = ((ScannableReference)avatarCrate).Barcode;
					obj = ((obj2 != null) ? obj2.ID : null);
				}
				if (obj == null)
				{
					obj = "";
				}
				return (string)obj;
			}
			catch
			{
				return "";
			}
		}
	}
	[HarmonyPatch(typeof(PlayerMarker))]
	internal static class PlayerMarkerPatches
	{
		public static PlayerMarker Instance { get; private set; }

		[HarmonyPatch("Awake")]
		[HarmonyPostfix]
		public static void AwakePostfix(PlayerMarker __instance)
		{
			Instance = __instance;
			Main.DebugLog("PlayerMarker component cached from Awake: " + ((Object)((Component)__instance).gameObject).name + ".");
		}

		public static void Clear()
		{
			Instance = null;
		}
	}
	internal static class RenderCaptureHook
	{
		private static int lastFrame = -1;

		private static Action<ScriptableRenderContext, Camera> callback;

		private static Action nextFrame;

		public static bool Active { get; private set; }

		public static void RunNextFrame(Action action)
		{
			Register();
			nextFrame = (Action)Delegate.Combine(nextFrame, action);
		}

		public static void Register()
		{
			if (Active)
			{
				return;
			}
			try
			{
				callback = Action<ScriptableRenderContext, Camera>.op_Implicit((Action<ScriptableRenderContext, Camera>)OnBeginCameraRendering);
				RenderPipelineManager.beginCameraRendering += callback;
				Active = true;
				Main.DebugLog("Render capture hook registered.");
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("Render capture hook failed, soft bones use the ArtRig pose instead: " + ex.Message);
			}
		}

		private static void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera)
		{
			int frameCount = Time.frameCount;
			if (frameCount == lastFrame)
			{
				return;
			}
			lastFrame = frameCount;
			if (nextFrame != null)
			{
				Action action = nextFrame;
				nextFrame = null;
				try
				{
					action();
				}
				catch (Exception value)
				{
					MelonLogger.Error($"End of frame action failed: {value}");
				}
			}
			try
			{
				Main instance = Main.Instance;
				if (instance != null && instance.recording)
				{
					instance.OnFrameRendered();
				}
			}
			catch (Exception value2)
			{
				MelonLogger.Error($"Capture frame failed: {value2}");
			}
		}
	}
	[HarmonyPatch(typeof(ArtRig), "ArtOutputLateUpdate")]
	internal static class ArtRigCapturePatch
	{
		[HarmonyPostfix]
		[HarmonyPriority(0)]
		public static void Postfix(ArtRig __instance)
		{
			Main instance = Main.Instance;
			if (instance != null && instance.recording && !(((Il2CppObjectBase)__instance).Pointer != instance.captureRigPointer))
			{
				instance.OnArtRigPosed();
			}
		}
	}
	internal sealed class CaptureSession
	{
		public const int Stride = 7;

		public readonly AvatarSkeleton skeleton;

		public readonly float frameRate;

		public readonly string originMode;

		public readonly Vector3 originPosition;

		public readonly Quaternion originRotation;

		public readonly bool fingerPhysics;

		public readonly DateTime started = DateTime.Now;

		private readonly Quaternion inverseOrigin;

		private readonly float inverseScale;

		private readonly Transform[] transforms;

		private readonly List<float> data = new List<float>();

		private readonly Vector3[] framePositions;

		private readonly RagdollRootFix ragdollFix;

		private readonly int[] softBones;

		private int capturedOnFrame = -1;

		private int framesThisUpdate;

		public int FrameCount { get; private set; }

		public bool RagdollActive => ragdollFix.Active;

		public int BoneCount => transforms.Length;

		public List<float> Data => data;

		public CaptureSession(AvatarSkeleton skeleton, float frameRate, string originMode, Vector3 originPosition, Quaternion originRotation, bool fingerPhysics)
		{
			//IL_0039: 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_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			this.skeleton = skeleton;
			this.frameRate = frameRate;
			this.originMode = originMode;
			this.originPosition = originPosition;
			this.originRotation = originRotation;
			this.fingerPhysics = fingerPhysics;
			inverseOrigin = Quaternion.Inverse(originRotation);
			inverseScale = 1f / skeleton.scale;
			transforms = (Transform[])(object)new Transform[skeleton.bones.Count];
			for (int i = 0; i < transforms.Length; i++)
			{
				transforms[i] = skeleton.bones[i].transform;
			}
			data.Capacity = transforms.Length * 7 * Mathf.CeilToInt(frameRate) * 30;
			framePositions = (Vector3[])(object)new Vector3[transforms.Length];
			ragdollFix = new RagdollRootFix(skeleton, frameRate);
			softBones = FindSoftBones(skeleton);
			Main.DebugLog($"Soft bones refreshed at end of frame: {softBones.Length}.");
		}

		internal static int[] FindSoftBones(AvatarSkeleton skeleton)
		{
			int count = skeleton.bones.Count;
			bool[] array = new bool[count];
			for (int i = 0; i < count; i++)
			{
				if (skeleton.bones[i].human.Length != 0)
				{
					for (int num = i; num >= 0; num = skeleton.bones[num].parent)
					{
						array[num] = true;
					}
				}
			}
			List<int> list = new List<int>();
			for (int j = 0; j < count; j++)
			{
				if (!array[j])
				{
					list.Add(j);
				}
			}
			return list.ToArray();
		}

		public bool Capture()
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: 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_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: 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_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: 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_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			for (int i = 0; i < transforms.Length; i++)
			{
				Transform val = transforms[i];
				if ((Object)(object)val == (Object)null)
				{
					return false;
				}
				framePositions[i] = inverseOrigin * (val.position - originPosition) * inverseScale;
			}
			ragdollFix.BeginFrame(framePositions);
			for (int j = 0; j < transforms.Length; j++)
			{
				Vector3 position = framePositions[j];
				Quaternion val2 = inverseOrigin * transforms[j].rotation;
				if (ragdollFix.IsRoot(j))
				{
					ragdollFix.Apply(j, ref position);
				}
				data.Add(position.x);
				data.Add(position.y);
				data.Add(position.z);
				data.Add(val2.x);
				data.Add(val2.y);
				data.Add(val2.z);
				data.Add(val2.w);
			}
			FrameCount++;
			capturedOnFrame = Time.frameCount;
			framesThisUpdate = 1;
			return true;
		}

		public void RepeatLastFrame()
		{
			if (FrameCount != 0)
			{
				int num = transforms.Length * 7;
				data.AddRange(data.GetRange(data.Count - num, num));
				FrameCount++;
				if (capturedOnFrame == Time.frameCount)
				{
					framesThisUpdate++;
				}
			}
		}

		public void RefreshSoftBones()
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: 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_0063: 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)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: 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_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			if (capturedOnFrame != Time.frameCount || softBones.Length == 0)
			{
				return;
			}
			int num = transforms.Length * 7;
			int[] array = softBones;
			foreach (int num2 in array)
			{
				Transform val = transforms[num2];
				if (!((Object)(object)val == (Object)null))
				{
					Vector3 val2 = inverseOrigin * (val.position - originPosition) * inverseScale;
					Quaternion val3 = inverseOrigin * val.rotation;
					for (int j = 0; j < framesThisUpdate; j++)
					{
						int num3 = data.Count - (j + 1) * num + num2 * 7;
						data[num3] = val2.x;
						data[num3 + 1] = val2.y;
						data[num3 + 2] = val2.z;
						data[num3 + 3] = val3.x;
						data[num3 + 4] = val3.y;
						data[num3 + 5] = val3.z;
						data[num3 + 6] = val3.w;
					}
				}
			}
			capturedOnFrame = -1;
		}

		public MotionClip ToClip()
		{
			int count = skeleton.bones.Count;
			MotionClip motionClip = new MotionClip
			{
				avatarName = skeleton.avatarName,
				barcode = skeleton.barcode,
				originMode = originMode,
				frameRate = frameRate,
				scale = skeleton.scale,
				fingerPhysics = fingerPhysics,
				hipsIndex = skeleton.HipsIndex,
				names = new string[count],
				human = new string[count],
				parents = new int[count],
				bindPose = new bool[count],
				rest = new float[count * 7],
				frames = data,
				frameCount = FrameCount
			};
			for (int i = 0; i < count; i++)
			{
				CapturedBone capturedBone = skeleton.bones[i];
				motionClip.names[i] = capturedBone.name;
				motionClip.human[i] = capturedBone.human;
				motionClip.parents[i] = capturedBone.parent;
				motionClip.bindPose[i] = capturedBone.bindPose;
				int num = i * 7;
				motionClip.rest[num] = capturedBone.restPosition.x;
				motionClip.rest[num + 1] = capturedBone.restPosition.y;
				motionClip.rest[num + 2] = capturedBone.restPosition.z;
				motionClip.rest[num + 3] = capturedBone.restRotation.x;
				motionClip.rest[num + 4] = capturedBone.restRotation.y;
				motionClip.rest[num + 5] = capturedBone.restRotation.z;
				motionClip.rest[num + 6] = capturedBone.restRotation.w;
			}
			return motionClip;
		}

		public static Quaternion Flatten(Quaternion rotation)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = rotation * Vector3.forward;
			val.y = 0f;
			if (((Vector3)(ref val)).sqrMagnitude < 1E-06f)
			{
				return Quaternion.identity;
			}
			return Quaternion.LookRotation(((Vector3)(ref val)).normalized, Vector3.up);
		}
	}
	internal static class PoseCapture
	{
		private const int Seconds = 3;

		private static bool counting;

		internal static bool QuietPopup { get; set; }

		public static bool Counting => counting;

		public static int SecondsLeft { get; private set; }

		public static float LastCaptureTime { get; private set; } = -100f;

		public static void Start(Main main)
		{
			if (counting)
			{
				Main.Send("BoneCap", "Already counting down", (NotificationType)1, 1.5f);
			}
			else
			{
				MelonCoroutines.Start(Countdown(main));
			}
		}

		private static string Label(int n)
		{
			return $"Capturing pose in {n}...";
		}

		private static IEnumerator Countdown(Main main)
		{
			counting = true;
			SecondsLeft = 3;
			Main.DebugLog($"Pose countdown started ({3}s).");
			HeadTitles titles = null;
			if (!QuietPopup)
			{
				Main.Send("BoneCap", Label(3), (NotificationType)0, 4.5f);
				float timeout = Time.unscaledTime + 5f;
				while (Time.unscaledTime < timeout)
				{
					titles = Titles();
					if (Showing(titles, Label(3)))
					{
						break;
					}
					yield return null;
				}
			}
			for (int n = 2; n >= 0; n--)
			{
				float timeout = Time.unscaledTime + 1f;
				while (Time.unscaledTime < timeout)
				{
					yield return null;
				}
				SecondsLeft = n;
				if (n > 0)
				{
					SetText(titles, Label(n));
				}
			}
			RenderCaptureHook.RunNextFrame(delegate
			{
				Capture(main, titles);
			});
			if (!RenderCaptureHook.Active)
			{
				Capture(main, titles);
			}
		}

		private static HeadTitles Titles()
		{
			try
			{
				TutorialRig instance = TutorialRig.Instance;
				return (instance != null) ? instance.headTitles : null;
			}
			catch
			{
				return null;
			}
		}

		private static bool Showing(HeadTitles titles, string text)
		{
			try
			{
				return (Object)(object)titles != (Object)null && ((Component)titles.headFollower).gameObject.activeInHierarchy && ((TMP_Text)titles.text_SubTitle).text == text;
			}
			catch
			{
				return false;
			}
		}

		private static void SetText(HeadTitles titles, string text)
		{
			try
			{
				if (!((Object)(object)titles == (Object)null) && ((TMP_Text)titles.text_SubTitle).text.StartsWith("Capturing pose"))
				{
					titles._string_SubTitle = text;
					((TMP_Text)titles.text_SubTitle).text = text;
				}
			}
			catch
			{
			}
		}

		private static void Capture(Main main, HeadTitles titles)
		{
			//IL_0051: 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)
			if (!counting)
			{
				return;
			}
			counting = false;
			try
			{
				if (!main.recording && !main.BuildSkeleton())
				{
					Main.Send("Error", "Rig or Avatar Not Found", (NotificationType)2, 3f);
					return;
				}
				main.ResolveOrigin(out var position, out var rotation, out var mode);
				CaptureSession captureSession = new CaptureSession(main.skeleton, main.frameRate, mode, position, rotation, FingerPhysicsFix.PhysicsFingers());
				if (!captureSession.Capture())
				{
					Main.Send("Error", "Pose capture failed", (NotificationType)2, 3f);
					return;
				}
				string text = Main.SaveCapture(captureSession.ToClip(), Main.PoseDirectory, $"Pose_{DateTime.Now:yyyyMMdd_HHmmss}");
				Main.DebugLog("Pose captured: " + text);
				LastCaptureTime = Time.unscaledTime;
				SetText(titles, "Pose Captured!");
			}
			catch (Exception value)
			{
				MelonLogger.Error($"Pose capture failed: {value}");
				Main.Send("Error", "Pose capture failed", (NotificationType)2, 3f);
			}
		}
	}
	internal sealed class RagdollRootFix
	{
		private readonly bool[] rootChain;

		private readonly int hipsIndex;

		private readonly int topIndex = -1;

		private readonly float[] heldOffset;

		private readonly float[] lastHeight;

		private readonly float[] exitCorrection;

		private readonly float blendStep;

		private readonly bool ragdollPlayerOn;

		private float bottom;

		private float exitBlend;

		public bool Active { get; private set; }

		public bool HasRootChain => topIndex >= 0;

		public RagdollRootFix(AvatarSkeleton skeleton, float frameRate)
		{
			int count = skeleton.bones.Count;
			hipsIndex = skeleton.HipsIndex;
			rootChain = new bool[count];
			heldOffset = new float[count];
			lastHeight = new float[count];
			exitCorrection = new float[count];
			blendStep = 1f / Mathf.Max(frameRate * 0.25f, 1f);
			for (int parent = skeleton.bones[hipsIndex].parent; parent >= 0; parent = skeleton.bones[parent].parent)
			{
				rootChain[parent] = true;
				topIndex = parent;
			}
			ragdollPlayerOn = RagdollPlayerFix.Enabled();
			RagdollPlayerFix.LogStatus();
		}

		public bool IsRoot(int index)
		{
			return rootChain[index];
		}

		public void BeginFrame(Vector3[] positions)
		{
			if (!HasRootChain || !ragdollPlayerOn)
			{
				return;
			}
			bottom = float.MaxValue;
			for (int i = 0; i < positions.Length; i++)
			{
				if (!rootChain[i] && positions[i].y < bottom)
				{
					bottom = positions[i].y;
				}
			}
			bool active = Active;
			Active = ragdollPlayerOn && RagdollPlayerFix.Ragdolled();
			if (active && !Active)
			{
				exitBlend = 1f;
				for (int j = 0; j < rootChain.Length; j++)
				{
					if (rootChain[j])
					{
						exitCorrection[j] = lastHeight[j] - positions[j].y;
					}
				}
			}
			else if (exitBlend > 0f)
			{
				exitBlend = Mathf.Max(exitBlend - blendStep, 0f);
			}
			if (!Active)
			{
				for (int k = 0; k < rootChain.Length; k++)
				{
					if (rootChain[k])
					{
						heldOffset[k] = positions[k].y - bottom;
					}
				}
			}
			if (Active != active)
			{
				Main.DebugLog(Active ? "Ragdoll engaged, root bones riding the bottom of the body." : "Ragdoll disengaged, root bones back to live tracking.");
			}
		}

		public void Apply(int index, ref Vector3 position)
		{
			if (Active)
			{
				position.y = bottom + heldOffset[index];
				exitBlend = 0f;
			}
			else if (exitBlend > 0f)
			{
				position.y += exitCorrection[index] * exitBlend;
			}
			lastHeight[index] = position.y;
		}
	}
}
namespace BONECAP.Menu
{
	internal static class BCmenu
	{
		public static void Build(Main main)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: 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)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			//IL_015a: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
			Page obj = Page.Root.CreatePage("BoneCap", Color.red, 0, true);
			obj.CreateFunction("Capture Motion", Color.blue, (Action)main.StartRecording);
			obj.CreateFunction("Stop Capture", Color.green, (Action)main.StopRecording);
			obj.CreateFunction("Capture Pose", Color.magenta, (Action)delegate
			{
				PoseCapture.Start(main);
			});
			Page obj2 = obj.CreatePage("Settings", Color.white, 0, true);
			Page obj3 = obj2.CreatePage("Capture Framerate", Color.cyan, 0, true);
			obj3.CreateFunction("120 FPS", Color.red, (Action)delegate
			{
				main.FrameRateSetting(120f);
			});
			obj3.CreateFunction("90 FPS", Color.magenta, (Action)delegate
			{
				main.FrameRateSetting(90f);
			});
			obj3.CreateFunction("60 FPS", Color.green, (Action)delegate
			{
				main.FrameRateSetting(60f);
			});
			obj3.CreateFunction("30 FPS", Color.yellow, (Action)delegate
			{
				main.FrameRateSetting(30f);
			});
			obj3.CreateFunction("20 FPS", Color.blue, (Action)delegate
			{
				main.FrameRateSetting(20f);
			});
			obj3.CreateFunction("12 FPS", Color.white, (Action)delegate
			{
				main.FrameRateSetting(12f);
			});
			obj2.CreateBool("Debug Mode", Color.yellow, Main.debugMode, (Action<bool>)delegate(bool val)
			{
				main.prefDebugMode.Value = val;
				MelonLogger.Msg("[BoneCap] Debug Mode " + (Main.debugMode ? "ON" : "OFF"));
			});
			obj2.CreateBool("Tracking Orgin PlayerMarker", Color.magenta, main.usePlayerMarker, (Action<bool>)delegate(bool val)
			{
				main.prefUsePlayerMarker.Value = val;
				Main.DebugLog($"Use PlayerMarker Origin set to {main.usePlayerMarker}.");
				if (val)
				{
					main.FindSceneRoot();
				}
			});
			obj2.CreateBool("SlowMo Bypass", Color.cyan, main.slowMoBypass, (Action<bool>)delegate(bool val)
			{
				main.prefSlowMoBypass.Value = val;
				Main.DebugLog($"SlowMo Bypass set to {val}.");
			});
		}
	}
}