//#define QUARANTINED_FEATURES using System.Numerics; using System.Runtime.InteropServices; using System.Globalization; using System.Text; using System.Text.Json; using ImGuiNET; using SharpPluginLoader.Core; using SharpPluginLoader.Core.MtTypes; using SharpPluginLoader.Core.Entities; using SharpPluginLoader.Core.Memory; using SharpPluginLoader.Core.Rendering; namespace NewCamera { using static Plugin; public unsafe class Poser { private const string posePath = "nativePC/plugins/CSharp/Poses"; private static class Armor { public const byte Body = 0; public const byte Helmet = 1; public const byte Arm = 2; public const byte Waist = 3; public const byte Leg = 4; public const byte Slinger = 5; public const byte Hair = 6; public const byte Face = 7; public const byte EyeLens = 8; } private static class Joint { public const byte Root = 0x0; public const byte Root2 = 0xD; public const byte LowerBack = 0x1; public const byte UpperBack = 0x2; public const byte LowerNeck = 0x3; public const byte LowerNeckNull = 0xFE; public const byte UpperNeck = 0x4; public const byte LeftCollarbone = 0x5; public const byte LeftShoulderNull = 0x46; public const byte LeftShoulder = 0x6; public const byte LeftElbow = 0x7; public const byte LeftWrist = 0x8; public const byte LeftWristNull = 0x1E; public const byte LeftThumbLow = 0x1F; public const byte LeftThumbMid = 0x20; public const byte LeftThumbHigh = 0x21; public const byte LeftIndexLow = 0x22; public const byte LeftIndexMid = 0x23; public const byte LeftIndexHigh = 0x24; public const byte LeftMiddleLow = 0x25; public const byte LeftMiddleMid = 0x26; public const byte LeftMiddleHigh = 0x27; public const byte LeftPalm = 0x28; public const byte LeftRingLow = 0x29; public const byte LeftRingMid = 0x2A; public const byte LeftRingHigh = 0x2B; public const byte LeftPinkyLow = 0x2C; public const byte LeftPinkyMid = 0x2D; public const byte LeftPinkyHigh = 0x2E; public const byte LeftArmUnk1 = 0x51; public const byte LeftArmUnk2 = 0x65; public const byte LeftArmUnk3 = 0x66; public const byte LeftArmUnk4 = 0x67; public const byte LeftArmUnk5 = 0x68; public const byte LeftArmUnk6 = 0x47; public const byte LeftArmUnk7 = 0x50; public const byte RightCollarbone = 0x9; public const byte RightShoulder = 0xA; public const byte RightElbow = 0xB; public const byte RightWrist = 0xC; public const byte RightWristNull = 0x2F; public const byte RightThumbLow = 0x30; public const byte RightThumbMid = 0x31; public const byte RightThumbHigh = 0x32; public const byte RightIndexLow = 0x33; public const byte RightIndexMid = 0x34; public const byte RightIndexHigh = 0x35; public const byte RightMiddleLow = 0x36; public const byte RightMiddleMid = 0x37; public const byte RightMiddleHigh = 0x38; public const byte RightPalm = 0x39; public const byte RightRingLow = 0x3A; public const byte RightRingMid = 0x3B; public const byte RightRingHigh = 0x3C; public const byte RightPinkyLow = 0x3D; public const byte RightPinkyMid = 0x3E; public const byte RightPinkyHigh = 0x3F; public const byte RightArmUnk1 = 0x53; public const byte RightArmUnk2 = 0x49; public const byte RightArmUnk3 = 0x52; public const byte RightArmUnk4 = 0x48; public const byte Unknown1 = 0xF7; public const byte Unknown2 = 0xF8; public const byte LeftHip = 0xE; public const byte LeftKnee = 0xF; public const byte LeftAnkle = 0x10; public const byte LeftToes = 0x11; public const byte LeftAnkleNull = 0x54; public const byte LeftKneeNull = 0x4B; public const byte LeftHipNull = 0x4A; public const byte Unknown3 = 0x40; public const byte Unknown4 = 0x42; public const byte RightHip = 0x12; public const byte RightKnee = 0x13; public const byte RightAnkle = 0x14; public const byte RightToes = 0x15; public const byte RightAnkleNull = 0x55; public const byte RightKneeNull = 0x4D; public const byte RightHipNull = 0x4C; public const byte Unknown5 = 0x43; public const byte Unknown6 = 0x45; public const byte Unknown7 = 0xF9; public const byte Unknown8 = 0xFA; public const byte Unknown9 = 0xFB; public const byte Unknown10 = 0xFC; public const byte Unknown11 = 0xFD; } private static class FaceJoint { public const byte LeftCheek1 = 0x6C; public const byte LeftCheek2 = 0x99; public const byte RightCheek1 = 0x71; public const byte RightCheek2 = 0x9E; public const byte RightCheek3 = 0x70; public const byte LeftCheek3 = 0x6B; public const byte RightCheek4 = 0x6F; public const byte LeftCheek4 = 0x6A; public const byte RightCheek5 = 0x72; public const byte LeftCheek5 = 0x6D; public const byte LeftCheek = 0x6E; public const byte RightCheek = 0x73; public const byte LeftEye = 0x3A; public const byte LeftEye1 = 0x3B; public const byte LeftEye2 = 0x43; public const byte LeftEye3 = 0x42; public const byte LeftEye4 = 0x41; public const byte LeftEye5 = 0x40; public const byte LeftEye6 = 0x3F; public const byte LeftEye7 = 0x46; public const byte LeftEye8 = 0x45; public const byte LeftEye9 = 0x44; public const byte LeftEye10 = 0x3C; public const byte LeftEye11 = 0x3E; public const byte LeftEye12 = 0x3D; public const byte LeftEye13 = 0x68; public const byte RightEye = 0x47; public const byte RightEye1 = 0x48; public const byte RightEye2 = 0x50; public const byte RightEye3 = 0x4F; public const byte RightEye4 = 0x4E; public const byte RightEye5 = 0x4D; public const byte RightEye6 = 0x4C; public const byte RightEye7 = 0x51; public const byte RightEye8 = 0x52; public const byte RightEye9 = 0x53; public const byte RightEye10 = 0x4A; public const byte RightEye11 = 0x4B; public const byte RightEye12 = 0x49; public const byte RightEye13 = 0x69; public const byte Nose = 0x54; public const byte Nose1 = 0x55; public const byte Nose2 = 0x56; public const byte Nose3 = 0x58; public const byte Nose4 = 0x64; public const byte Nose5 = 0x5E; public const byte Nose6 = 0x5F; public const byte Nose7 = 0x65; public const byte Nose8 = 0x5C; public const byte Nose9 = 0x62; public const byte Nose10 = 0x5D; public const byte Nose11 = 0x63; public const byte Nose12 = 0x5B; public const byte Nose13 = 0x61; public const byte Nose14 = 0x5A; public const byte Nose15 = 0x60; public const byte Nose16 = 0x57; public const byte Nose17 = 0x59; public const byte Nose18 = 0x66; public const byte Nose19 = 0x67; public const byte Mouth = 0x75; public const byte Mouth1 = 0x80; public const byte Mouth2 = 0x81; public const byte Mouth3 = 0x86; public const byte Mouth4 = 0x85; public const byte Mouth5 = 0x84; public const byte Mouth6 = 0x83; public const byte Mouth7 = 0x82; public const byte Mouth8 = 0x8B; public const byte Mouth9 = 0x8A; public const byte Mouth10 = 0x89; public const byte Mouth11 = 0x88; public const byte Mouth12 = 0x87; public const byte Mouth13 = 0x8F; public const byte Mouth14 = 0x8E; public const byte Mouth15 = 0x90; public const byte Mouth16 = 0x8D; public const byte Mouth17 = 0x91; public const byte Mouth18 = 0x8C; public const byte Mouth19 = 0x92; public const byte Mouth20 = 0x76; public const byte Mouth21 = 0x77; public const byte Mouth22 = 0x78; public const byte Mouth23 = 0x79; public const byte Mouth24 = 0x7A; public const byte Mouth25 = 0x7F; public const byte Mouth26 = 0x7E; public const byte Mouth27 = 0x7D; public const byte Mouth28 = 0x7C; public const byte Mouth29 = 0x7B; public const byte Mouth30 = 0x74; public const byte ChinMiddle = 0x94; public const byte ChinRight = 0x9C; public const byte ChinLeft = 0x9B; public const byte ChinRightBack = 0x9D; public const byte ChinLeftBack = 0x9A; public const byte NeckMiddle = 0xA0; public const byte Jaw = 0x97; public const byte Jaw1 = 0xEA; public const byte Jaw2 = 0xEB; public const byte JawLeft = 0x98; public const byte JawRight = 0x96; public const byte JawUpLeft = 0x93; public const byte JawUpRight = 0x95; public const byte NeckLeft = 0x9F; public const byte NeckRight = 0xA1; public const byte EyeAgainLeft = 0xF4; public const byte EyeAgainLeft1 = 0xF5; public const byte NoseAgain = 0xF6; public const byte MouthAgain = 0xF7; public const byte Forehead0 = 0xF8; public const byte Forehead = 0xA2; public const byte Forehead1 = 0x36; public const byte Forehead2 = 0x37; public const byte Forehead3 = 0x38; public const byte Forehead4 = 0x35; public const byte Forehead5 = 0x34; public const byte Forehead6 = 0x33; public const byte Forehead7 = 0x32; public const byte Forehead8 = 0x2E; public const byte Forehead9 = 0x2D; public const byte Forehead10 = 0x2F; public const byte Forehead11 = 0x2C; public const byte Forehead12 = 0x30; public const byte Forehead13 = 0x31; public const byte Forehead14 = 0x39; public const byte Base1 = 0xFB; public const byte Base2 = 0xFC; public const byte Base3 = 0xFD; } private static nint[] playerArmor = new nint[9]; private static List playerWeapon = new List(); private static nint[] bodyJointMap = new nint[0xFF]; private static nint[] faceJointMap = new nint[0xFF]; private static int lastArmorRefresh = -1; private delegate void UpdateJoints(nint obj); private static Hook? updateJointRotationHook; private static Hook? updateJointPositionHook; public static List bodyJoints = new List(); private static List ignoredJoints = new List(); private static Dictionary bodyRotations = new Dictionary(); private static Dictionary bodyPositions = new Dictionary(); private static Dictionary bodyOffsets = new Dictionary(); private static List faceJoints = new List(); private static Dictionary faceRotations = new Dictionary(); private static Dictionary facePositions = new Dictionary(); private static Dictionary faceOffsets = new Dictionary(); private static List jiggleBones = new List(); private static bool ikForcedOff = false; private static Patch forceDisableIK; #if QUARANTINED_FEATURES private delegate void UpdateIKPosition(nint superOfChild, nint joint, nint obj); private static Hook? updateIKPositionHook; private static List ikJoints = new List(); private static Dictionary ikOffsets = new Dictionary(); #endif public static void Initialize() { forceDisableIK = new Patch(0x14031FD40, [0xC6, 0x81, 0x81, 0x01, 0x00, 0x00, 0x00, 0xC3]); updateJointRotationHook = Hook.Create(0x14223BA30, UpdateJointRotationHook); updateJointPositionHook = Hook.Create(0x141FBCCD0, UpdateJointPositionHook); #if QUARANTINED_FEATURES updateIKPositionHook = Hook.Create(0x142472C00, UpdateIKPositionHook); #endif } private static void dropArmorState() { Array.Clear(playerArmor, 0, playerArmor.Length); playerWeapon.Clear(); Array.Clear(bodyJointMap, 0, bodyJointMap.Length); Array.Clear(faceJointMap, 0, faceJointMap.Length); bodyJoints.Clear(); ignoredJoints.Clear(); faceJoints.Clear(); #if QUARANTINED_FEATURES ikJoints.Clear(); #endif jiggleBones.Clear(); PlayerExt.Attach.DropState(); } private static void dropJointOffsets() { bodyRotations.Clear(); bodyPositions.Clear(); bodyOffsets.Clear(); faceRotations.Clear(); facePositions.Clear(); faceOffsets.Clear(); #if QUARANTINED_FEATURES ikOffsets.Clear(); #endif } private static void refreshArmorState(Player player) { dropArmorState(); if (!collectArmorAndJoints(player)) { dropJointOffsets(); } /* if (playerArmor[Armor.Waist] != 0x0) { if (hideKnife) { MemoryUtil.GetRef(playerArmor[Armor.Waist] + 0x2D0) &= 0xFD; } else { MemoryUtil.GetRef(playerArmor[Armor.Waist] + 0x2D0) |= 0x2; } } */ lastArmorRefresh = updateTick; } private static void collectArmorParts(nint baseAddr, nint addr) { int part; string fullString = Marshal.PtrToStringAnsi(addr + 0xC)!; string partName = Models.extractPartName(fullString); if (partName == "body") part = Armor.Body; else if (partName == "helm") part = Armor.Helmet; else if (partName == "arm") part = Armor.Arm; else if (partName == "wst") part = Armor.Waist; else if (partName == "leg") part = Armor.Leg; else if (partName.StartsWith("slg")) part = Armor.Slinger; else if (partName.StartsWith("hair")) part = Armor.Hair; else if (partName == "face000") part = Armor.Face; else if (partName == "face000_eyelens") part = Armor.EyeLens; else if (fullString.StartsWith("wp\\")) { playerWeapon.Add(baseAddr); return; } else return; playerArmor[part] = baseAddr; int jointCount = MemoryUtil.Read(baseAddr + 0x4A0); nint jointAddr = MemoryUtil.Read(baseAddr + 0x4A8); for (int i = 0; i < jointCount; i++) { nint childAddr = MemoryUtil.Read(jointAddr + 0x8); if (childAddr != 0x0 && MemoryUtil.Read(childAddr) == 0x142F21A90) { childAddr = MemoryUtil.Read(childAddr + 0x8); if (childAddr != 0x0) { nint childVtable = MemoryUtil.Read(childAddr); if (childVtable == 0x1435195B8) // Jiggle bone container. { if (part == Armor.Body) { ignoredJoints.Add(jointAddr); } } else if (childVtable == 0x143524BD8) { childAddr = MemoryUtil.Read(childAddr + 0x188); if (childAddr != 0x0 && MemoryUtil.Read(childAddr) == 0x143519530) { nint jiggleData = MemoryUtil.Read(MemoryUtil.Read(childAddr + 0xA0)); if (MemoryUtil.Read(jiggleData) == 0x143519508) { if (!jiggleBones.Contains(jiggleData)) { int jiggleIndex = jiggleBones.Count(); jiggleBones.Add(jiggleData); PlayerExt.Attach.OnBone(jiggleData, jiggleIndex, part == Armor.Body); } } } if (part == Armor.Body) { ignoredJoints.Add(jointAddr); } } #if QUARANTINED_FEATURES else if (childVtable == 0x143249170) { childAddr = MemoryUtil.Read(childAddr + 0x188); if (childAddr != 0x0 && MemoryUtil.Read(childAddr) == 0x1432492F0) { ikJoints.Add(jointAddr); } } #endif } } if (part == Armor.Body) { bodyJoints.Add(jointAddr); } else if (part == Armor.Face) { faceJoints.Add(jointAddr); } jointAddr += 0xC0; } if (part == Armor.Body) { for (int i = 0; i < bodyJoints.Count; i++) { jointAddr = bodyJoints[i]; if (ignoredJoints.Contains(jointAddr)) { continue; } byte id = MemoryUtil.Read(jointAddr + 0xA0); Assert(bodyJointMap[id] == 0x0); bodyJointMap[id] = jointAddr; } foreach ((nint joint, _) in bodyRotations.Where((e) => !bodyJoints.Contains(e.Key))) bodyRotations.Remove(joint); foreach ((nint joint, _) in bodyPositions.Where((e) => !bodyJoints.Contains(e.Key))) bodyPositions.Remove(joint); foreach ((nint joint, _) in bodyOffsets.Where((e) => !bodyJoints.Contains(e.Key))) bodyOffsets.Remove(joint); #if QUARANTINED_FEATURES foreach ((nint joint, _) in ikOffsets.Where((e) => !bodyJoints.Contains(e.Key))) ikOffsets.Remove(joint); #endif } else if (part == Armor.Face) { for (int i = 0; i < faceJoints.Count; i++) { jointAddr = faceJoints[i]; byte id = MemoryUtil.Read(jointAddr + 0xA0); Assert(faceJointMap[id] == 0x0); faceJointMap[id] = jointAddr; } foreach ((nint joint, _) in faceRotations.Where((e) => !faceJoints.Contains(e.Key))) faceRotations.Remove(joint); foreach ((nint joint, _) in facePositions.Where((e) => !faceJoints.Contains(e.Key))) facePositions.Remove(joint); foreach ((nint joint, _) in faceOffsets.Where((e) => !faceJoints.Contains(e.Key))) faceOffsets.Remove(joint); } } private static bool collectArmorAndJoints(Player player) { nint bodyArmorParts = MemoryUtil.Read(player.Instance + 0x2A0); if (bodyArmorParts == 0x0) { return false; } // MonsterHunterWorld.exe+203E8A2 - mov rax,[rbx+000126C8] nint combineArmorList = MemoryUtil.Read(player.Instance + 0x126C8); if (combineArmorList == 0x0) { return false; } collectArmorParts(player.Instance, bodyArmorParts); nint next, partsAddr; nint weapon = MemoryUtil.Read(player.Instance + 0x76B0); if (weapon != 0x0) { partsAddr = MemoryUtil.Read(weapon + 0x2A0); if (partsAddr != 0x0) { collectArmorParts(weapon, partsAddr); } nint weaponParts = MemoryUtil.Read(weapon + 0x21D8); next = weaponParts; for (;next != 0x0;) { weaponParts = next; next = MemoryUtil.Read(weaponParts + 0x30); nint vTable = MemoryUtil.Read(weaponParts); if (vTable == 0x1434D1D58) // uWeaponEmblem { if (MemoryUtil.Read(weaponParts + 0x590) != player.Instance) { continue; } nint parentAddr = MemoryUtil.Read(weaponParts + 0x5A8); if (parentAddr != 0x0) { vTable = MemoryUtil.Read(parentAddr); if (vTable == 0x1434D7C88 || vTable == 0x1434D7928) // uWpSubGeneral || uWpQuiver { if (MemoryUtil.Read(parentAddr + 0x9B0) != player.Instance) { continue; } partsAddr = MemoryUtil.Read(parentAddr + 0x2A0); if (partsAddr != 0x0) { collectArmorParts(parentAddr, partsAddr); } } } } else if (vTable == 0x1434D2400) // uWeaponParts { if (MemoryUtil.Read(weaponParts + 0x990) != player.Instance) { continue; } } else { continue; } partsAddr = MemoryUtil.Read(weaponParts + 0x2A0); if (partsAddr != 0x0) { collectArmorParts(weaponParts, partsAddr); } } } nint slinger = MemoryUtil.Read(player.Instance + 0x8918); if (slinger != 0x0) { partsAddr = MemoryUtil.Read(slinger + 0x2A0); if (partsAddr != 0x0) { collectArmorParts(slinger, partsAddr); } } combineArmorList = MemoryUtil.Read(combineArmorList + 0x70); next = combineArmorList; for (;next != 0x0;) { nint armorAddr = next; next = MemoryUtil.Read(armorAddr + 0x30); nint vTable = MemoryUtil.Read(armorAddr); if (vTable == 0x1434A7560) // uPlCombineArmor { nint parentAddr = MemoryUtil.Read(armorAddr + 0x548); if (parentAddr != 0x0) { if (MemoryUtil.Read(parentAddr) == 0x143451520) // Face. { if (MemoryUtil.Read(parentAddr + 0x998) != player.Instance) { continue; } if (playerArmor[Armor.Face] == 0x0) { partsAddr = MemoryUtil.Read(parentAddr + 0x2A0); if (partsAddr != 0x0) { collectArmorParts(parentAddr, partsAddr); } nint childAddr = MemoryUtil.Read(parentAddr + 0x9C8); if (childAddr != 0x0 && MemoryUtil.Read(childAddr) == 0x143505E98) // Eye lens. { partsAddr = MemoryUtil.Read(childAddr + 0x2A0); if (partsAddr != 0x0) { collectArmorParts(childAddr, partsAddr); } } } } else if (parentAddr != player.Instance) { continue; } } } else { continue; } partsAddr = MemoryUtil.Read(armorAddr + 0x2A0); if (partsAddr != 0x0) { collectArmorParts(armorAddr, partsAddr); } if (!playerArmor.Where((p) => p == 0x0).Any()) { break; } } return true; } private static void UpdateJointRotationHook(nint model) { debugLog($"UpdateJointRotationHook(0x{model:X}) @ {updateTick}"); Player? player = null; bool updateBody = bodyRotations.Count() > 0 || (enableFreeCamera && orbitPlayer && orbitJoint >= 0); bool updateFace = faceRotations.Count() > 0; if (updateBody || updateFace) { player = PlayerExt.getPlayerWithFallback(); } if (updateBody) { if (player != null && model == player.Instance) { refreshArmorState(player); foreach ((nint addr, Vector4 rotation) in bodyRotations) { MemoryUtil.GetRef(addr + 0x70) = new Quaternion(rotation.X, rotation.Y, rotation.Z, rotation.W); } } } if (updateFace) { if (player != null && model == playerArmor[Armor.Face]) { if (!updateBody) { refreshArmorState(player); } else { Assert(lastArmorRefresh == updateTick); } foreach ((nint addr, Vector4 rotation) in faceRotations) { MemoryUtil.GetRef(addr + 0x70) = new Quaternion(rotation.X, rotation.Y, rotation.Z, rotation.W); } } } updateJointRotationHook!.Original(model); } private static void UpdateJointPositionHook(nint model) { debugLog($"UpdateJointPositionHook(0x{model:X}) @ {updateTick}"); updateJointPositionHook!.Original(model); Player? player = null; bool updateBody = bodyPositions.Count() > 0 || bodyOffsets.Count() > 0; bool updateFace = facePositions.Count() > 0 || faceOffsets.Count() > 0; if (updateBody || updateFace) { player = PlayerExt.getPlayerWithFallback(); } if (updateBody) { if (player != null && model == player.Instance) { refreshArmorState(player); Quaternion rootRotation = new Quaternion(player.Rotation.X, player.Rotation.Y, player.Rotation.Z, player.Rotation.W); Vector3 rootForward = Vector3.Transform(new Vector3(0.0f, 0.0f, 1.0f), rootRotation); Vector3 rootRight = Vector3.Transform(new Vector3(1.0f, 0.0f, 0.0f), rootRotation); Vector3 rootUp = new Vector3(0.0f, 1.0f, 0.0f); foreach ((nint addr, Vector3 position) in bodyPositions) { MemoryUtil.GetRef(addr + 0x50) = player.Position + ((rootForward * position.X) + (rootUp * position.Y) + (rootRight * position.Z)); } foreach ((nint addr, Vector3 offset) in bodyOffsets) { MemoryUtil.GetRef(addr + 0x50) += ((rootForward * offset.X) + (rootUp * offset.Y) + (rootRight * offset.Z)); } } } if (updateFace) { if (player != null && model == playerArmor[Armor.Face]) { if (!updateBody) { refreshArmorState(player); } else { Assert(lastArmorRefresh == updateTick); } Quaternion rootRotation = new Quaternion(player.Rotation.X, player.Rotation.Y, player.Rotation.Z, player.Rotation.W); Vector3 rootForward = Vector3.Transform(new Vector3(0.0f, 0.0f, 1.0f), rootRotation); Vector3 rootRight = Vector3.Transform(new Vector3(1.0f, 0.0f, 0.0f), rootRotation); Vector3 rootUp = new Vector3(0.0f, 1.0f, 0.0f); foreach ((nint addr, Vector3 position) in facePositions) { MemoryUtil.GetRef(addr + 0x50) = player.Position + ((rootForward * position.X) + (rootUp * position.Y) + (rootRight * position.Z)); } foreach ((nint addr, Vector3 offset) in faceOffsets) { MemoryUtil.GetRef(addr + 0x50) += ((rootForward * offset.X) + (rootUp * offset.Y) + (rootRight * offset.Z)); } } } } #if QUARANTINED_FEATURES private static void UpdateIKPositionHook(nint superOfChild, nint joint, nint obj) { debugLog($"UpdateIKPositionHook(0x{superOfChild:X}, 0x{joint:X}, 0x{obj:X}) @ {updateTick}"); if (ikOffsets.ContainsKey(joint)) { Vector3 offset = ikOffsets[joint]; MemoryUtil.GetRef(superOfChild + 0x1380) += offset; } updateIKPositionHook!.Original(superOfChild, joint, obj); } #endif private static void drawJoint(nint jointAddr, Dictionary rotations, Dictionary offsets) { if (jointAddr == 0x0) return; ImGui.PushID(jointAddr); bool hasRotation = rotations.ContainsKey(jointAddr); bool hasOffset = offsets.ContainsKey(jointAddr); #if QUARANTINED_FEATURES bool hasIkOffset = ikOffsets.ContainsKey(jointAddr); #endif ref Vector3 position = ref MemoryUtil.GetRef(jointAddr + 0x50); ref Vector4 rotation = ref MemoryUtil.GetRef(jointAddr + 0x70); ImGui.DragFloat4("Rotation", ref rotation, 0.005f); if (hasRotation) { ImGui.PushID("Rotation"); bool doRemove = ImGui.Button("X"); ImGui.SameLine(); if (ImGui.Button("Reset")) { rotations[jointAddr] = default; } ImGui.SameLine(); if (Models.DrawRotate("##Rotate", rotations[jointAddr], out Vector4 result)) { rotations[jointAddr] = result; } if (doRemove) rotations.Remove(jointAddr); ImGui.PopID(); } ImGui.DragFloat3("Position", ref position, 0.005f); if (hasOffset) { ImGui.PushID("Position"); bool doRemove = ImGui.Button("X"); ImGui.SameLine(); if (ImGui.Button("Reset")) { offsets[jointAddr] = default; } ImGui.SameLine(); Vector3 v = offsets[jointAddr]; if (ImGui.DragFloat3($"##Offset", ref v, 0.005f)) { offsets[jointAddr] = v; } if (doRemove) offsets.Remove(jointAddr); ImGui.PopID(); } #if QUARANTINED_FEATURES if (hasIkOffset) { ImGui.PushID("IK"); bool doRemove = ImGui.Button("X"); ImGui.SameLine(); if (ImGui.Button("Reset")) { ikOffsets[jointAddr] = default; } ImGui.SameLine(); Vector3 v = ikOffsets[jointAddr]; if (ImGui.DragFloat3($"##IK Offset", ref v, 0.025f)) { ikOffsets[jointAddr] = v; } if (doRemove) ikOffsets.Remove(jointAddr); ImGui.PopID(); } #endif if (!hasRotation) { if (ImGui.Button("Add Rotation")) { rotations.Add(jointAddr, rotation); } } if (!hasOffset) { if (!hasRotation) { ImGui.SameLine(); } if (ImGui.Button("Add Offset")) { offsets.Add(jointAddr, new Vector3()); } } #if QUARANTINED_FEATURES if (ikJoints.Contains(jointAddr) && !hasIkOffset) { if (!hasOffset) { ImGui.SameLine(); } if (ImGui.Button("Add IK Offset")) { ikOffsets.Add(jointAddr, new Vector3()); } } #endif ImGui.PopID(); } private static void drawBodyJoint(nint jointAddr) { drawJoint(jointAddr, bodyRotations, bodyOffsets); } private static void drawFaceJoint(nint jointAddr) { drawJoint(jointAddr, faceRotations, faceOffsets); } private static void drawJointLeaf(ref List joints, int i, Dictionary rotations, Dictionary offsets) { if (i >= joints.Count) return; nint jointAddr = joints[i]; byte id = MemoryUtil.Read(jointAddr + 0xA0); ImGui.PushID(jointAddr); if (ImGui.CollapsingHeader($"Joint #{i} (0x{jointAddr:X}, id: 0x{id:X})")) { drawJoint(jointAddr, rotations, offsets); } ImGui.PopID(); drawJointLeaf(ref joints, i + 1, rotations, offsets); } private static void drawBodyJoints() { ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags.Framed; bool expanded; if (ImGui.TreeNodeEx("Root", flags)) { drawBodyJoint(bodyJointMap[Joint.Root2]); ImGui.TreePop(); } ImGui.PushID("Neck"); if (ImGui.TreeNodeEx("Neck", flags)) { if (ImGui.TreeNodeEx("Upper", flags)) { drawBodyJoint(bodyJointMap[Joint.UpperNeck]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Lower", flags)) { drawBodyJoint(bodyJointMap[Joint.LowerNeck]); ImGui.TreePop(); } ImGui.TreePop(); } ImGui.PopID(); // Neck. ImGui.PushID("Back"); if (ImGui.TreeNodeEx("Back", flags)) { if (ImGui.TreeNodeEx("Upper", flags)) { drawBodyJoint(bodyJointMap[Joint.UpperBack]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Lower", flags)) { drawBodyJoint(bodyJointMap[Joint.LowerBack]); ImGui.TreePop(); } ImGui.TreePop(); } ImGui.PopID(); // Back. ImGui.PushID("Right Arm"); if (ImGui.TreeNodeEx("Right Arm", flags)) { if (ImGui.TreeNodeEx("Collarbone", flags)) { drawBodyJoint(bodyJointMap[Joint.RightCollarbone]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Shoulder", flags)) { drawBodyJoint(bodyJointMap[Joint.RightShoulder]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Elbow", flags)) { drawBodyJoint(bodyJointMap[Joint.RightElbow]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Wrist", flags)) { drawBodyJoint(bodyJointMap[Joint.RightWrist]); ImGui.TreePop(); } ImGui.TreePop(); } ImGui.PopID(); // Right Arm. ImGui.PushID("Right Hand"); if (ImGui.TreeNodeEx("Right Hand", flags)) { ImGui.PushID("Thumb"); if (ImGui.TreeNodeEx("Thumb", flags)) { if (ImGui.TreeNodeEx("Base", flags)) { drawBodyJoint(bodyJointMap[Joint.RightThumbLow]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Middle", flags)) { drawBodyJoint(bodyJointMap[Joint.RightThumbMid]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Tip", flags)) { drawBodyJoint(bodyJointMap[Joint.RightThumbHigh]); ImGui.TreePop(); } ImGui.TreePop(); } ImGui.PopID(); ImGui.PushID("Index"); if (ImGui.TreeNodeEx("Index", flags)) { if (ImGui.TreeNodeEx("Base", flags)) { drawBodyJoint(bodyJointMap[Joint.RightIndexLow]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Middle", flags)) { drawBodyJoint(bodyJointMap[Joint.RightIndexMid]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Tip", flags)) { drawBodyJoint(bodyJointMap[Joint.RightIndexHigh]); ImGui.TreePop(); } ImGui.TreePop(); } ImGui.PopID(); ImGui.PushID("Middle"); if (ImGui.TreeNodeEx("Middle", flags)) { if (ImGui.TreeNodeEx("Base", flags)) { drawBodyJoint(bodyJointMap[Joint.RightMiddleLow]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Middle", flags)) { drawBodyJoint(bodyJointMap[Joint.RightMiddleMid]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Tip", flags)) { drawBodyJoint(bodyJointMap[Joint.RightMiddleHigh]); ImGui.TreePop(); } ImGui.TreePop(); } ImGui.PopID(); ImGui.PushID("Ring"); if (ImGui.TreeNodeEx("Ring", flags)) { if (ImGui.TreeNodeEx("Base", flags)) { drawBodyJoint(bodyJointMap[Joint.RightRingLow]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Middle", flags)) { drawBodyJoint(bodyJointMap[Joint.RightRingMid]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Tip", flags)) { drawBodyJoint(bodyJointMap[Joint.RightRingHigh]); ImGui.TreePop(); } ImGui.TreePop(); } ImGui.PopID(); ImGui.PushID("Pinky"); if (ImGui.TreeNodeEx("Pinky", flags)) { if (ImGui.TreeNodeEx("Base", flags)) { drawBodyJoint(bodyJointMap[Joint.RightPinkyLow]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Middle", flags)) { drawBodyJoint(bodyJointMap[Joint.RightPinkyMid]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Tip", flags)) { drawBodyJoint(bodyJointMap[Joint.RightPinkyHigh]); ImGui.TreePop(); } ImGui.TreePop(); } ImGui.PopID(); expanded = ImGui.TreeNodeEx("Palm", flags); if (ImGui.BeginItemTooltip()) { ImGui.Text("Outer Palm/Pinky+Ring"); ImGui.EndTooltip(); } if (expanded) { drawBodyJoint(bodyJointMap[Joint.RightPalm]); ImGui.TreePop(); } ImGui.TreePop(); } ImGui.PopID(); // Right Hand. ImGui.PushID("Right Leg"); if (ImGui.TreeNodeEx("Right Leg", flags)) { if (ImGui.TreeNodeEx("Hip", flags)) { drawBodyJoint(bodyJointMap[Joint.RightHip]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Knee", flags)) { drawBodyJoint(bodyJointMap[Joint.RightKnee]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Ankle", flags)) { drawBodyJoint(bodyJointMap[Joint.RightAnkle]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Toes", flags)) { drawBodyJoint(bodyJointMap[Joint.RightToes]); ImGui.TreePop(); } ImGui.TreePop(); } ImGui.PopID(); // Right Leg. ImGui.PushID("Left Arm"); if (ImGui.TreeNodeEx("Left Arm", flags)) { if (ImGui.TreeNodeEx("Collarbone", flags)) { drawBodyJoint(bodyJointMap[Joint.LeftCollarbone]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Shoulder", flags)) { drawBodyJoint(bodyJointMap[Joint.LeftShoulder]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Elbow", flags)) { drawBodyJoint(bodyJointMap[Joint.LeftElbow]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Wrist", flags)) { drawBodyJoint(bodyJointMap[Joint.LeftWrist]); ImGui.TreePop(); } ImGui.TreePop(); } ImGui.PopID(); // Left Arm. ImGui.PushID("Left Hand"); if (ImGui.TreeNodeEx("Left Hand", flags)) { ImGui.PushID("Thumb"); if (ImGui.TreeNodeEx("Thumb", flags)) { if (ImGui.TreeNodeEx("Base", flags)) { drawBodyJoint(bodyJointMap[Joint.LeftThumbLow]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Middle", flags)) { drawBodyJoint(bodyJointMap[Joint.LeftThumbMid]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Tip", flags)) { drawBodyJoint(bodyJointMap[Joint.LeftThumbHigh]); ImGui.TreePop(); } ImGui.TreePop(); } ImGui.PopID(); ImGui.PushID("Index"); if (ImGui.TreeNodeEx("Index", flags)) { if (ImGui.TreeNodeEx("Base", flags)) { drawBodyJoint(bodyJointMap[Joint.LeftIndexLow]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Middle", flags)) { drawBodyJoint(bodyJointMap[Joint.LeftIndexMid]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Tip", flags)) { drawBodyJoint(bodyJointMap[Joint.LeftIndexHigh]); ImGui.TreePop(); } ImGui.TreePop(); } ImGui.PopID(); ImGui.PushID("Middle"); if (ImGui.TreeNodeEx("Middle", flags)) { if (ImGui.TreeNodeEx("Base", flags)) { drawBodyJoint(bodyJointMap[Joint.LeftMiddleLow]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Middle", flags)) { drawBodyJoint(bodyJointMap[Joint.LeftMiddleMid]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Tip", flags)) { drawBodyJoint(bodyJointMap[Joint.LeftMiddleHigh]); ImGui.TreePop(); } ImGui.TreePop(); } ImGui.PopID(); ImGui.PushID("Ring"); if (ImGui.TreeNodeEx("Ring", flags)) { if (ImGui.TreeNodeEx("Base", flags)) { drawBodyJoint(bodyJointMap[Joint.LeftRingLow]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Middle", flags)) { drawBodyJoint(bodyJointMap[Joint.LeftRingMid]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Tip", flags)) { drawBodyJoint(bodyJointMap[Joint.LeftRingHigh]); ImGui.TreePop(); } ImGui.TreePop(); } ImGui.PopID(); ImGui.PushID("Pinky"); if (ImGui.TreeNodeEx("Pinky", flags)) { if (ImGui.TreeNodeEx("Base", flags)) { drawBodyJoint(bodyJointMap[Joint.LeftPinkyLow]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Middle", flags)) { drawBodyJoint(bodyJointMap[Joint.LeftPinkyMid]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Tip", flags)) { drawBodyJoint(bodyJointMap[Joint.LeftPinkyHigh]); ImGui.TreePop(); } ImGui.TreePop(); } ImGui.PopID(); expanded = ImGui.TreeNodeEx("Palm", flags); if (ImGui.BeginItemTooltip()) { ImGui.Text("Outer Palm/Pinky+Ring"); ImGui.EndTooltip(); } if (expanded) { drawBodyJoint(bodyJointMap[Joint.LeftPalm]); ImGui.TreePop(); } ImGui.TreePop(); } ImGui.PopID(); // Left Hand. ImGui.PushID("Left Leg"); if (ImGui.TreeNodeEx("Left Leg", flags)) { if (ImGui.TreeNodeEx("Hip", flags)) { drawBodyJoint(bodyJointMap[Joint.LeftHip]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Knee", flags)) { drawBodyJoint(bodyJointMap[Joint.LeftKnee]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Ankle", flags)) { drawBodyJoint(bodyJointMap[Joint.LeftAnkle]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Toes", flags)) { drawBodyJoint(bodyJointMap[Joint.LeftToes]); ImGui.TreePop(); } ImGui.TreePop(); } ImGui.PopID(); // Left Leg. } private static void drawFaceJoints() { ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags.Framed; ImGui.PushID("Lips"); if (ImGui.TreeNodeEx("Lips", flags)) { if (ImGui.TreeNodeEx("Right Edge", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth2]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Right Lower 1", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth3]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Right Lower 2", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth4]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Right Upper 1", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth29]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Right Upper 2", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth28]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Left Edge", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth1]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Left Lower 1", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth7]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Left Lower 2", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth6]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Left Upper 1", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth25]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Left Upper 2", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth26]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Middle Lower", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth5]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Middle Upper", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth27]); ImGui.TreePop(); } ImGui.TreePop(); } ImGui.PopID(); // Lips. ImGui.PushID("Mouth"); if (ImGui.TreeNodeEx("Mouth", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth]); if (ImGui.TreeNodeEx("Bottom Teeth", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth30]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Right Lower 1", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth12]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Right Lower 2", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth11]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Right Upper 1", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth24]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Right Upper 2", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth23]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Left Lower 1", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth8]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Left Lower 2", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth9]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Left Upper 1", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth20]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Left Upper 2", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth21]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Middle Lower", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth10]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Middle Upper", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth22]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Right Under", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth15]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Left Under", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth14]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Middle Under", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth13]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Right Cheek 1", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth17]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Right Cheek 2", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth19]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Left Cheek 1", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth16]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Left Cheek 2", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Mouth18]); ImGui.TreePop(); } ImGui.TreePop(); } ImGui.PopID(); // Mouth. ImGui.PushID("Cheeks"); if (ImGui.TreeNodeEx("Cheeks", flags)) { if (ImGui.TreeNodeEx("Right Cheek", flags)) { drawFaceJoint(faceJointMap[FaceJoint.RightCheek]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Right Cheek 1", flags)) { drawFaceJoint(faceJointMap[FaceJoint.RightCheek1]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Right Cheek 2", flags)) { drawFaceJoint(faceJointMap[FaceJoint.RightCheek2]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Right Cheek 3", flags)) { drawFaceJoint(faceJointMap[FaceJoint.RightCheek3]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Right Cheek 4", flags)) { drawFaceJoint(faceJointMap[FaceJoint.RightCheek4]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Right Cheek 5", flags)) { drawFaceJoint(faceJointMap[FaceJoint.RightCheek5]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Left Cheek", flags)) { drawFaceJoint(faceJointMap[FaceJoint.LeftCheek]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Left Cheek 1", flags)) { drawFaceJoint(faceJointMap[FaceJoint.LeftCheek1]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Left Cheek 2", flags)) { drawFaceJoint(faceJointMap[FaceJoint.LeftCheek2]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Left Cheek 3", flags)) { drawFaceJoint(faceJointMap[FaceJoint.LeftCheek3]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Left Cheek 4", flags)) { drawFaceJoint(faceJointMap[FaceJoint.LeftCheek4]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Left Cheek 5", flags)) { drawFaceJoint(faceJointMap[FaceJoint.LeftCheek5]); ImGui.TreePop(); } ImGui.TreePop(); } ImGui.PopID(); // Cheeks. ImGui.PushID("Right Eye"); if (ImGui.TreeNodeEx("Right Eye", flags)) { drawFaceJoint(faceJointMap[FaceJoint.RightEye]); if (ImGui.TreeNodeEx("Eyeball", flags)) { drawFaceJoint(faceJointMap[FaceJoint.RightEye1]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Eyelid Outer", flags)) { drawFaceJoint(faceJointMap[FaceJoint.RightEye2]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Eyelid 1", flags)) { drawFaceJoint(faceJointMap[FaceJoint.RightEye3]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Eyelid 2", flags)) { drawFaceJoint(faceJointMap[FaceJoint.RightEye4]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Eyelid 3", flags)) { drawFaceJoint(faceJointMap[FaceJoint.RightEye5]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Eyelid Inner", flags)) { drawFaceJoint(faceJointMap[FaceJoint.RightEye6]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Eyelid Lower 1", flags)) { drawFaceJoint(faceJointMap[FaceJoint.RightEye7]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Eyelid Lower 2", flags)) { drawFaceJoint(faceJointMap[FaceJoint.RightEye8]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Eyelid Lower 3", flags)) { drawFaceJoint(faceJointMap[FaceJoint.RightEye9]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Eyelid Top 1", flags)) { drawFaceJoint(faceJointMap[FaceJoint.RightEye10]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Eyelid Top 2", flags)) { drawFaceJoint(faceJointMap[FaceJoint.RightEye11]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Eyelid Top 3", flags)) { drawFaceJoint(faceJointMap[FaceJoint.RightEye12]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Under Eye", flags)) { drawFaceJoint(faceJointMap[FaceJoint.RightEye13]); ImGui.TreePop(); } ImGui.TreePop(); } ImGui.PopID(); // Right Eye. ImGui.PushID("Left Eye"); if (ImGui.TreeNodeEx("Left Eye", flags)) { drawFaceJoint(faceJointMap[FaceJoint.LeftEye]); if (ImGui.TreeNodeEx("Eyeball", flags)) { drawFaceJoint(faceJointMap[FaceJoint.LeftEye1]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Eyelid Outer", flags)) { drawFaceJoint(faceJointMap[FaceJoint.LeftEye2]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Eyelid 1", flags)) { drawFaceJoint(faceJointMap[FaceJoint.LeftEye3]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Eyelid 2", flags)) { drawFaceJoint(faceJointMap[FaceJoint.LeftEye4]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Eyelid 3", flags)) { drawFaceJoint(faceJointMap[FaceJoint.LeftEye5]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Eyelid Inner", flags)) { drawFaceJoint(faceJointMap[FaceJoint.LeftEye6]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Eyelid Lower 1", flags)) { drawFaceJoint(faceJointMap[FaceJoint.LeftEye7]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Eyelid Lower 2", flags)) { drawFaceJoint(faceJointMap[FaceJoint.LeftEye8]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Eyelid Lower 3", flags)) { drawFaceJoint(faceJointMap[FaceJoint.LeftEye9]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Eyelid Top 1", flags)) { drawFaceJoint(faceJointMap[FaceJoint.LeftEye10]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Eyelid Top 2", flags)) { drawFaceJoint(faceJointMap[FaceJoint.LeftEye11]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Eyelid Top 3", flags)) { drawFaceJoint(faceJointMap[FaceJoint.LeftEye12]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Under Eye", flags)) { drawFaceJoint(faceJointMap[FaceJoint.LeftEye13]); ImGui.TreePop(); } ImGui.TreePop(); } ImGui.PopID(); // Left Eye. ImGui.PushID("Chin"); if (ImGui.TreeNodeEx("Chin", flags)) { if (ImGui.TreeNodeEx("Chin Right", flags)) { drawFaceJoint(faceJointMap[FaceJoint.ChinRight]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Chin Right Back", flags)) { drawFaceJoint(faceJointMap[FaceJoint.ChinRightBack]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Chin Middle", flags)) { drawFaceJoint(faceJointMap[FaceJoint.ChinMiddle]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Chin Left", flags)) { drawFaceJoint(faceJointMap[FaceJoint.ChinLeft]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Chin Left Back", flags)) { drawFaceJoint(faceJointMap[FaceJoint.ChinLeftBack]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Neck Right", flags)) { drawFaceJoint(faceJointMap[FaceJoint.NeckRight]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Neck Middle", flags)) { drawFaceJoint(faceJointMap[FaceJoint.NeckMiddle]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Neck Left", flags)) { drawFaceJoint(faceJointMap[FaceJoint.NeckLeft]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Jaw", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Jaw]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Jaw Right", flags)) { drawFaceJoint(faceJointMap[FaceJoint.JawRight]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Jaw Up Right", flags)) { drawFaceJoint(faceJointMap[FaceJoint.JawUpRight]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Jaw Left", flags)) { drawFaceJoint(faceJointMap[FaceJoint.JawLeft]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Jaw Up Left", flags)) { drawFaceJoint(faceJointMap[FaceJoint.JawUpLeft]); ImGui.TreePop(); } ImGui.TreePop(); } ImGui.PopID(); // Chin. ImGui.PushID("Brow"); if (ImGui.TreeNodeEx("Brow", flags)) { if (ImGui.TreeNodeEx("Brow 0", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Forehead0]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Brow", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Forehead]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Brow 1", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Forehead1]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Brow 2", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Forehead2]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Brow 3", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Forehead3]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Brow 4", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Forehead4]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Brow 5", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Forehead5]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Brow 6", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Forehead6]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Brow 7", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Forehead7]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Brow 8", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Forehead8]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Brow 9", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Forehead9]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Brow 10", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Forehead10]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Brow 11", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Forehead11]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Brow 12", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Forehead12]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Brow 13", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Forehead13]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Brow 14", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Forehead14]); ImGui.TreePop(); } ImGui.TreePop(); } ImGui.PopID(); // Brow. ImGui.PushID("Nose"); if (ImGui.TreeNodeEx("Nose", flags)) { if (ImGui.TreeNodeEx("Nose", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Nose]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Nose 1", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Nose1]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Nose 2", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Nose2]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Nose 3", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Nose3]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Nose 4", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Nose4]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Nose 5", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Nose5]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Nose 6", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Nose6]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Nose 7", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Nose7]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Nose 8", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Nose8]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Nose 9", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Nose9]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Nose 10", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Nose10]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Nose 11", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Nose11]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Nose 12", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Nose12]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Nose 13", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Nose13]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Nose 14", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Nose14]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Nose 15", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Nose15]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Nose 16", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Nose16]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Nose 17", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Nose17]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Nose 18", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Nose18]); ImGui.TreePop(); } if (ImGui.TreeNodeEx("Nose 19", flags)) { drawFaceJoint(faceJointMap[FaceJoint.Nose19]); ImGui.TreePop(); } ImGui.TreePop(); } ImGui.PopID(); // Nose. } private static string typedPoseName = ""; private static string selectedPoseName = ""; private static string[] poseList = []; private static bool poseSelectionStarted = false; private static float StringToSingle(string s) { return Convert.ToSingle(s, CultureInfo.InvariantCulture); } public static Vector3 StringToVector3(string s, char delim = ',') { if (String.IsNullOrEmpty(s)) throw new FormatException(); string[] sp = s.Split(delim).Select((v) => v.Trim()).ToArray(); if (sp.Length != 3) throw new FormatException(); return new Vector3(StringToSingle(sp[0]), StringToSingle(sp[1]), StringToSingle(sp[2])); } public static Vector4 StringToVector4(string s, char delim = ',') { if (String.IsNullOrEmpty(s)) throw new FormatException(); string[] sp = s.Split(delim).Select((v) => v.Trim()).ToArray(); if (sp.Length != 4) throw new FormatException(); return new Vector4(StringToSingle(sp[0]), StringToSingle(sp[1]), StringToSingle(sp[2]), StringToSingle(sp[3])); } private static string Vector3ToString(Vector3 v) { return string.Format(CultureInfo.InvariantCulture, "{0},{1},{2}", v.X, v.Y, v.Z); } private static string Vector4ToString(Vector4 v) { return string.Format(CultureInfo.InvariantCulture, "{0},{1},{2},{3}", v.X, v.Y, v.Z, v.W); } public static bool overlayJoints = false; public static void DrawUI(float width, Player player) { bool armorStateRefreshed = false; ImGui.PushID("Joints"); if (ImGui.CollapsingHeader("Joints")) { if (!armorStateRefreshed) { refreshArmorState(player); armorStateRefreshed = true; } ImGui.Checkbox("Overlay Joints", ref overlayJoints); ImGui.SetCursorPos(ImGui.GetCursorPos() + new Vector2(0.0f, 6.0f)); #if QUARANTINED_FEATURES ImGui.Text($"Rotations: {bodyRotations.Count + faceRotations.Count}, Positions: {bodyPositions.Count + facePositions.Count}, Offsets: {bodyOffsets.Count + faceOffsets.Count + ikOffsets.Count}"); #else ImGui.Text($"Rotations: {bodyRotations.Count + faceRotations.Count}, Positions: {bodyPositions.Count + facePositions.Count}, Offsets: {bodyOffsets.Count + faceOffsets.Count}"); #endif ImGui.SameLine(); ImGui.SetCursorPos(ImGui.GetCursorPos() - new Vector2(0.0f, 6.0f)); if (ImGui.Button("Clear")) { dropJointOffsets(); } /* ref byte ikDisabledB = ref MemoryUtil.GetRef(MemoryUtil.Read(player.Instance + 0x990) + 0x5C); bool ikDisabled = ikDisabledB == 0x0; if (ImGui.Checkbox("Disable IK", ref ikDisabled)) { ikDisabledB = ByteFlag(!ikDisabled); } if (ImGui.BeginItemTooltip()) { ImGui.Text("Mostly disable inverse kinematics on your player. To rotate ankles, you need to use Force Disable IK."); ImGui.EndTooltip(); } ImGui.SameLine(); */ if (ImGui.Checkbox("Disable IK", ref ikForcedOff)) { if (ikForcedOff) { forceDisableIK.Enable(); } else { forceDisableIK.Disable(); } } if (ImGui.BeginItemTooltip()) { ImGui.Text("Forcefully disable inverse kinematics."); ImGui.EndTooltip(); } ImGui.SameLine(); ref byte lookAtDisabledB = ref MemoryUtil.GetRef(MemoryUtil.Read(player.Instance + 0x9C0) + 0x5C); bool lookAtDisabled = lookAtDisabledB == 0x0; if (ImGui.Checkbox("Disable Look At", ref lookAtDisabled)) { lookAtDisabledB = ByteFlag(!lookAtDisabled); } if (ImGui.BeginItemTooltip()) { ImGui.Text("Stop your character from looking at people, animals, and objects they pass."); ImGui.EndTooltip(); } ImGui.PushItemWidth(width * 0.3f); ImGui.InputText("##Pose Name", ref typedPoseName, 99); ImGui.SameLine(); if (ImGui.Button("Save")) { string name = typedPoseName; if (name != "") { if (!Directory.Exists(posePath)) { Directory.CreateDirectory(posePath); } string filename = name + ".json"; string path = Path.Join(posePath, filename); using (FileStream stream = File.Open(path, FileMode.Create)) { using (Utf8JsonWriter writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true })) { Vector3 rootPosition = player.Position; Quaternion rootRotation = new Quaternion(player.Rotation.X, player.Rotation.Y, player.Rotation.Z, player.Rotation.W); Vector3 rootForward = Vector3.Transform(new Vector3(0.0f, 0.0f, 1.0f), rootRotation); Vector3 rootRight = Vector3.Transform(new Vector3(1.0f, 0.0f, 0.0f), rootRotation); Vector3 rootUp = new Vector3(0.0f, 1.0f, 0.0f); writer.WriteStartObject(); writer.WriteStartObject("body"); foreach (nint jointAddr in bodyJoints) { byte id = MemoryUtil.Read(jointAddr + 0xA0); writer.WritePropertyName($"{id}"); writer.WriteStartArray(); writer.WriteStringValue(Vector4ToString(MemoryUtil.GetRef(jointAddr + 0x70))); Vector3 positionOffset = default; Vector3 position = MemoryUtil.GetRef(jointAddr + 0x50); Vector3 diff = position - rootPosition; positionOffset.Y = diff.Y; diff.Y = 0.0f; positionOffset.X = Vector3.Dot(rootForward, diff); positionOffset.Z = Vector3.Dot(rootRight, diff); writer.WriteStringValue(Vector3ToString(positionOffset)); if (bodyOffsets.ContainsKey(jointAddr)) { writer.WriteStringValue(Vector3ToString(bodyOffsets[jointAddr])); } writer.WriteEndArray(); } writer.WriteEndObject(); writer.WriteStartObject("face"); foreach (nint jointAddr in faceJoints) { byte id = MemoryUtil.Read(jointAddr + 0xA0); writer.WritePropertyName($"{id}"); writer.WriteStartArray(); writer.WriteStringValue(Vector4ToString(MemoryUtil.GetRef(jointAddr + 0x70))); Vector3 positionOffset = default; Vector3 position = MemoryUtil.GetRef(jointAddr + 0x50); Vector3 diff = position - rootPosition; positionOffset.Y = diff.Y; diff.Y = 0.0f; positionOffset.X = Vector3.Dot(rootForward, diff); positionOffset.Z = Vector3.Dot(rootRight, diff); writer.WriteStringValue(Vector3ToString(positionOffset)); if (faceOffsets.ContainsKey(jointAddr)) { writer.WriteStringValue(Vector3ToString(faceOffsets[jointAddr])); } writer.WriteEndArray(); } writer.WriteEndObject(); writer.WriteEndObject(); writer.Flush(); } } } } ImGui.SameLine(); if (ImGui.BeginCombo("##Poses", selectedPoseName, ImGuiComboFlags.HeightLarge)) { if (!poseSelectionStarted) { poseList = Directory.GetFiles(posePath); poseSelectionStarted = true; } foreach (string filePath in poseList) { if (Path.GetExtension(filePath) != ".json") { continue; } string fileName = Path.GetFileNameWithoutExtension(filePath); bool isSelected = fileName == selectedPoseName; if (ImGui.Selectable(fileName, isSelected)) { dropJointOffsets(); selectedPoseName = fileName; Utf8JsonReader reader = new Utf8JsonReader(File.ReadAllBytes(filePath)); bool isFace = false; byte currentId = 0xFF; int arrayIndex = 0; while (reader.Read()) { switch (reader.TokenType) { case JsonTokenType.PropertyName: string property = reader.GetString()!; if (property == "body") { isFace = false; } else if (property == "face") { isFace = true; } else { try { currentId = Convert.ToByte(property); } catch (FormatException) { currentId = 0xFF; } catch (OverflowException) { currentId = 0xFF; } } break; case JsonTokenType.StartArray: arrayIndex = 0; break; case JsonTokenType.String: if (currentId == 0xFF) break; if (arrayIndex == 0) { if (isFace) { faceRotations[faceJointMap[currentId]] = StringToVector4(reader.GetString()!); } else { bodyRotations[bodyJointMap[currentId]] = StringToVector4(reader.GetString()!); } } else if (arrayIndex == 1) { if (isFace) { facePositions[faceJointMap[currentId]] = StringToVector3(reader.GetString()!); } else { bodyPositions[bodyJointMap[currentId]] = StringToVector3(reader.GetString()!); } } arrayIndex++; break; } } } if (isSelected) ImGui.SetItemDefaultFocus(); } ImGui.EndCombo(); } else { poseSelectionStarted = false; } ImGui.SameLine(); if (ImGui.Button("Delete")) { if (selectedPoseName != "") { selectedPoseName = ""; } } ImGui.PopItemWidth(); ImGui.PushItemWidth(width * 0.75f); drawBodyJoints(); ImGui.Separator(); if (ImGui.CollapsingHeader("Face")) { drawFaceJoints(); ImGui.Separator(); } if (ImGui.CollapsingHeader("Jiggle Bones")) { for (int i = 0; i < jiggleBones.Count; i++) { nint jiggleData = jiggleBones[i]; ImGui.PushID(jiggleData); if (ImGui.CollapsingHeader($"Jiggle Bone #{i} (0x{jiggleData:X})")) { ImGui.DragFloat3("Position", ref MemoryUtil.GetRef(jiggleData + 0xD0), 0.05f); if (ImGui.BeginItemTooltip()) { ImGui.Text("Drag this position around to influence the bone."); ImGui.EndTooltip(); } } ImGui.PopID(); } } ImGui.Separator(); if (ImGui.CollapsingHeader("All Joints")) { if (ImGui.CollapsingHeader("Body Joints")) { drawJointLeaf(ref bodyJoints, 0, bodyRotations, bodyOffsets); } if (ImGui.CollapsingHeader("Face Joints")) { drawJointLeaf(ref faceJoints, 0, faceRotations, faceOffsets); } } ImGui.PopItemWidth(); ImGui.Separator(); } ImGui.PopID(); ImGui.PushID("Parts"); if (ImGui.CollapsingHeader("Armor Parts")) { if (!armorStateRefreshed) { refreshArmorState(player); armorStateRefreshed = true; } bool expaned = ImGui.CollapsingHeader("Helmet"); ImGui.SameLine(); ImGui.Text($"(id: {MemoryUtil.Read(player.Instance + 0x1373C):X})"); if (expaned) { Models.drawModelParts(playerArmor[Armor.Helmet]); ImGui.Separator(); } expaned = ImGui.CollapsingHeader("Body"); ImGui.SameLine(); ImGui.Text($"(id: {MemoryUtil.Read(player.Instance + 0x13740):X})"); if (expaned) { Models.drawModelParts(playerArmor[Armor.Body]); ImGui.Separator(); } expaned = ImGui.CollapsingHeader("Arms"); ImGui.SameLine(); ImGui.Text($"(id: {MemoryUtil.Read(player.Instance + 0x13744):X})"); if (expaned) { Models.drawModelParts(playerArmor[Armor.Arm]); ImGui.Separator(); } expaned = ImGui.CollapsingHeader("Waist"); ImGui.SameLine(); ImGui.Text($"(id: {MemoryUtil.Read(player.Instance + 0x13748):X})"); if (expaned) { Models.drawModelParts(playerArmor[Armor.Waist]); ImGui.Separator(); } expaned = ImGui.CollapsingHeader("Legs"); ImGui.SameLine(); ImGui.Text($"(id: {MemoryUtil.Read(player.Instance + 0x1374C):X})"); if (expaned) { Models.drawModelParts(playerArmor[Armor.Leg]); ImGui.Separator(); } expaned = ImGui.CollapsingHeader("Slinger"); ImGui.SameLine(); ImGui.Text($"(id: {MemoryUtil.Read(player.Instance + 0x13D8C):X}_{MemoryUtil.Read(player.Instance + 0x13D90):X})"); if (expaned) { Models.drawModelParts(playerArmor[Armor.Slinger]); ImGui.Separator(); } if (ImGui.CollapsingHeader("Hair")) { Models.drawModelParts(playerArmor[Armor.Hair]); ImGui.Separator(); } if (ImGui.CollapsingHeader("Face")) { Models.drawModelParts(playerArmor[Armor.Face]); ImGui.Separator(); } if (ImGui.CollapsingHeader("Eye Lens")) { Models.drawModelParts(playerArmor[Armor.EyeLens]); } ImGui.Separator(); } if (ImGui.CollapsingHeader("Weapon Parts")) { bool expaned = ImGui.CollapsingHeader("Weapon"); if (expaned) { foreach (nint weaponPart in playerWeapon) { Models.drawModelParts(weaponPart); ImGui.Separator(); } } } ImGui.PopID(); } public static void OnRender() { if (overlayJoints) { for (int i = 0; i < faceJoints.Count; i++) { nint jointAddr = faceJoints[i]; ref Vector3 position = ref MemoryUtil.GetRef(jointAddr + 0x50); Primitives.RenderSphere(position, 0.1f, new MtColor(255, 0, 0, 255)); } } } } }