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