diff options
| author | 2026-10-09 13:13:59 -0400 | |
|---|---|---|
| committer | 2026-10-09 13:13:59 -0400 | |
| commit | a66fbdfab71ece98223d257f3a6da7ab0339ee1f (patch) | |
| tree | 5289018b0b7130d9509eff971b060d0699f71b92 /Models.cs | |
| parent | 8abc6082c701084d505fa4fdf391a8cbf69e4fec (diff) | |
| download | NewCamera-master.tar.gz NewCamera-master.tar.bz2 NewCamera-master.zip | |
Signed-off-by: Andrew Opalach <andrew@akon.city>
Diffstat (limited to 'Models.cs')
| -rw-r--r-- | Models.cs | 576 |
1 files changed, 576 insertions, 0 deletions
diff --git a/Models.cs b/Models.cs new file mode 100644 index 0000000..b0872d9 --- /dev/null +++ b/Models.cs @@ -0,0 +1,576 @@ +using System.Numerics; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; + +using ImGuiNET; +using SharpPluginLoader.Core; +using SharpPluginLoader.Core.Memory; +using SharpPluginLoader.Core.Entities; +using SharpPluginLoader.Core.Actions; +using SharpPluginLoader.Core.Components; + +namespace NewCamera +{ + using static Plugin; + + public unsafe class Models + { + private static string modelFilter = ""; + private static bool modelFilterIsRegex = false; + private static Regex? modelRegex = null; + public static bool modelOnlyAddressLod0 = false; + + private static string objectFilter = ""; + private static bool sortObjectsByDistanceToPlayer = false; + + private static int inputLmt = -1; + private static int inputAnimationId = -1; + private static int inputSet = -1; + private static int inputActionId = -1; + + public static bool DrawRotate(string label, Vector4 rotation, out Vector4 result) + { + Vector3 offset = new Vector3(); + if (ImGui.DragFloat3(label, ref offset, 0.0025f)) + { + Quaternion rotationQ = new Quaternion(rotation.X, rotation.Y, rotation.Z, rotation.W); + rotationQ *= Quaternion.CreateFromYawPitchRoll(offset.X, offset.Y, offset.Z); + result = new Vector4(rotationQ.X, rotationQ.Y, rotationQ.Z, rotationQ.W); + return true; + } + result = new Vector4(-1.0f); + return false; + } + + public static string extractPartName(string fullString) + { + string partName = ""; + if (fullString.StartsWith("pl\\f_equip") || fullString.StartsWith("pl\\m_equip")) + { + partName = fullString.Split('\\')[3]; + } + else if (fullString.StartsWith("wp\\")) + { + string[] split = fullString.Split('\\'); + if (split[4] == "mod") + { + partName = split[5]; + } + else + { + partName = split[4]; + } + } + else if (fullString.StartsWith("pl\\hair")) + { + partName = fullString.Split('\\')[2]; + } + else if (fullString.StartsWith("pl\\f_face") || fullString.StartsWith("pl\\m_face")) + { + partName = fullString.Split('\\')[4].Substring(2); + } + return partName; + } + + public static void drawModelParts(nint armorAddr, nint partsAddr = 0x0, bool hideAll = false, bool showAll = false) + { + if (partsAddr == 0x0) + { + if (armorAddr == 0x0 || (partsAddr = MemoryUtil.Read<nint>(armorAddr + 0x2A0)) == 0x0) + { + return; + } + } + ImGui.PushID(partsAddr); + string fullString = Marshal.PtrToStringAnsi(partsAddr + 0xC)!; + if (armorAddr != 0x0) + { + string partName = extractPartName(fullString); + ImGui.Text($"{partName} (0x{armorAddr:X}, {fullString} @ 0x{partsAddr:X}):"); + } + else + { + ImGui.Text($"{fullString} @ 0x{partsAddr:X}:"); + } + // MonsterHunterWorld.exe+222E651 - mov rax,[r8+000000C8] + nint baseLod = MemoryUtil.Read<nint>(partsAddr + 0xC8); + nint baseOffset = MemoryUtil.Read<nint>(partsAddr + 0x408 + (0x8 * 0x1)); + int numParts = MemoryUtil.Read<int>(partsAddr + 0x548 + (0x4 * 0x1)); + // Setting lodCount to 0 is probably the best way to completely hide a model. + int lodCount = MemoryUtil.Read<int>(partsAddr + 0xD0); + const int lodStructSize = 0x50; + const int partsPerLine = 10; + bool allOn = ImGui.Button("Show All") || showAll; + ImGui.SameLine(); + bool allOff = ImGui.Button("Hide All") || hideAll; + for (int i = 0; i < numParts; i++) + { + ref int lodOffset = ref MemoryUtil.GetRef<int>(baseOffset + (i * 0x4)); + nint lod0 = baseLod + lodOffset * lodStructSize; + bool isVisible = MemoryUtil.GetRef<ushort>(lod0) != 0; + bool togglePart = ImGui.Checkbox($"##Part #{i}", ref isVisible); + if (ImGui.BeginItemTooltip()) + { + ImGui.Text($"Part #{i} (0x{lod0:X})"); + ImGui.EndTooltip(); + } + int lastLod = (i < numParts - 1) ? MemoryUtil.Read<int>(baseOffset + ((i + 1) * 0x4)) : lodCount; + int partLodCount = lastLod - lodOffset; + // This is not a "proper" way to hide a part. It abuses the fact that if the lower + // 16 bits (lodVisible) of these 4 bytes are 0, a check to skip rendering it will + // always pass. Also that +0xA appears to be unused/padding. + // MonsterHunterWorld.exe+222FCE3 - mov eax,[r8] + ushort lod0Visible = MemoryUtil.GetRef<ushort>(lod0); + for (int j = 0; j < partLodCount; j++) + { + if (togglePart || (lod0Visible != 0 && allOff) || (lod0Visible == 0 && allOn)) + { + nint lod = lod0 + (j * lodStructSize); + ref ushort lodVisible = ref MemoryUtil.GetRef<ushort>(lod); + ref ushort padSub = ref MemoryUtil.GetRef<ushort>(lod + 0xA); + if (lod0Visible != 0) Assert(padSub == 0); + // Check against LOD0 to prevent an inconsistent state. + if ((lodVisible != 0) == (lod0Visible != 0)) + { + (lodVisible, padSub) = (padSub, lodVisible); + } + } + if (modelOnlyAddressLod0) break; + } + if (i != numParts - 1 && i % partsPerLine != partsPerLine - 1) + { + ImGui.SameLine(); + } + } + ImGui.PopID(); + } + + public static void drawAnimationInfo(Entity entity, float width, bool debug) + { + ActionController actionController = entity.ActionController; + ActionInfo currentActionInfo = actionController.CurrentAction; + SharpPluginLoader.Core.Actions.Action? currentAction = null; + if (currentActionInfo.ActionSet >= 0 && currentActionInfo.ActionSet <= 3) + { + ActionList actionList = actionController.GetActionList(currentActionInfo.ActionSet); + if (currentActionInfo.ActionId >= 0 && currentActionInfo.ActionId < actionList.Count) + { + currentAction = actionList[currentActionInfo.ActionId]; + } + } + if (debug) + { + ImGui.Text($"ActionController: 0x{actionController.Instance:X}"); + ImGui.Text($" Current: {currentAction} {currentActionInfo}"); + if (currentAction != null) + { + + ImGui.Text($" Active Time: {currentAction.ActiveTime}"); + ImGui.Text($" Delta Sec: {currentAction.DeltaSec}"); + } + } + AnimationId currentAnimation = entity.CurrentAnimation; + AnimationLayerComponent? animationLayer = entity.AnimationLayer; + if (animationLayer != null) + { + ImGui.Text(debug ? $"AnimationLayer: 0x{animationLayer.Instance:X}" : "Animation"); + + if (debug) + { + ImGui.Text($" Current: {currentAnimation}"); + ImGui.Text($" Frame: {animationLayer.CurrentFrame:0.000}/{animationLayer.MaxFrame:0.000}"); + } + else + { + ImGui.Text($" Current: {currentAnimation.Lmt}.{currentAnimation.Id}, {(currentAction != null ? currentAction.Name : "None")}"); + if (ImGui.BeginItemTooltip()) + { + ImGui.Text("{Lmt}.{Id}, {Action}"); + ImGui.EndTooltip(); + } + ImGui.SetNextItemWidth(width * 0.8f); + ImGui.SliderFloat("Frame", ref animationLayer.CurrentFrame, 0.0f, animationLayer.MaxFrame, "%.3f"); + bool animationPaused = animationLayer.Paused; + if (ImGui.Checkbox("Paused", ref animationPaused)) + { + animationLayer.Paused = animationPaused; + } + ImGui.SameLine(); + float? lockedSpeed = animationLayer.LockedSpeed; + float speed = lockedSpeed ?? animationLayer.Speed; + bool animationLocked = lockedSpeed != null; + if (ImGui.Checkbox("Set", ref animationLocked)) + { + if (animationLocked) + { + animationLayer.LockSpeed(speed); + } + else + { + animationLayer.UnlockSpeed(); + } + } + ImGui.SameLine(); + ImGui.SetNextItemWidth(width * 0.3f); + if (ImGui.DragFloat($"Speed", ref speed, 0.01f, 0.0f, 0.0f, "%.3f")) + { + if (animationLocked) animationLayer.LockSpeed(speed); + } + } + + if (debug) + { + ImGui.PushItemWidth(width * 0.125f); + ImGui.PushID("Animation"); + ImGui.InputInt("Lmt", ref inputLmt, 1); + ImGui.SameLine(); + ImGui.InputInt("Animation Id", ref inputAnimationId, 1); + if (!(inputLmt >= 0 && inputAnimationId >= 0)) + { + ImGui.PushItemFlag(ImGuiItemFlags.Disabled, true); + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, ImGui.GetStyle().Alpha * 0.5f); + } + bool doAnimation = ImGui.Button("Do"); + if (!(inputLmt >= 0 && inputAnimationId >= 0)) + { + ImGui.PopStyleVar(); + ImGui.PopItemFlag(); + } + ImGui.PopID(); + ImGui.PushID("Action"); + ImGui.InputInt("Set", ref inputSet, 1); + ImGui.SameLine(); + ImGui.InputInt("Action Id", ref inputActionId, 1); + if (!(inputSet >= 0 && inputActionId >= 0)) + { + ImGui.PushItemFlag(ImGuiItemFlags.Disabled, true); + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, ImGui.GetStyle().Alpha * 0.5f); + } + bool doAction = ImGui.Button("Do"); + if (!(inputSet >= 0 && inputActionId >= 0)) + { + ImGui.PopStyleVar(); + ImGui.PopItemFlag(); + } + ImGui.PopID(); + if (ImGui.Button("Do Both")) + { + doAnimation = true; + doAction = true; + } + if (doAction) + { + MemoryUtil.WriteBytes(actionController.Instance + 0xC0, [(byte)inputActionId, 0x00, 0x00, 0x00]); + MemoryUtil.WriteBytes(actionController.Instance + 0xBC, [(byte)inputSet, 0x00, 0x00, 0x00]); + } + if (doAnimation) + { + animationLayer.DoAnimation(new AnimationId((uint)inputLmt, (uint)inputAnimationId)); + } + ImGui.PopItemWidth(); + } + } + } + + private static void iterateList(nint baseAddr, Action<nint> func) + { + nint next = baseAddr; + nint head = next; + nint prev = MemoryUtil.Read<nint>(next + 0x10); + for (;next != 0x0;) + { + func(next + 0x40); + if (head == prev) + { + next = MemoryUtil.Read<nint>(next + 0x10); + } + else + { + next = MemoryUtil.Read<nint>(next + 0x18); + if (next == 0x0) + { + head = prev; + next = prev; + } + } + } + } + + public static void DrawModelsList(float width, nint baseObject) + { + ImGui.PushID("Models"); + ImGui.Checkbox("Only Toggle LOD 0", ref modelOnlyAddressLod0); + if (ImGui.BeginItemTooltip()) + { + ImGui.Text($"This can be used to check if a given model is showing LOD 0 or not.\nApplies when a part is toggled, meaning you should probably toggle all parts on before enabling this."); + ImGui.EndTooltip(); + } + if (ImGui.CollapsingHeader("Loaded Models") && baseObject != 0x0) + { + ImGui.Text("Filter"); + bool updateRegex = false; + bool invalidRegex = modelFilterIsRegex && modelFilter != "" && modelRegex == null; + if (invalidRegex) + { + ImGui.PushStyleColor(ImGuiCol.Text, 0xFF0000FF); + } + ImGui.SetNextItemWidth(width * 0.55f); + if (ImGui.InputText("##Filter", ref modelFilter, 256)) + { + if (modelFilterIsRegex) + { + updateRegex = true; + } + } + if (invalidRegex) + { + ImGui.PopStyleColor(); + } + ImGui.SameLine(); + if (ImGui.Button("Clear")) + { + modelFilter = ""; + if (modelFilterIsRegex) + { + updateRegex = true; + } + } + ImGui.SameLine(); + if (ImGui.Checkbox("Regex", ref modelFilterIsRegex)) + { + if (modelFilterIsRegex) + { + updateRegex = true; + } + } + if (updateRegex) + { + if (String.IsNullOrEmpty(modelFilter)) + { + modelRegex = null; + } + else + { + try + { + modelRegex = new Regex(modelFilter); + } + catch (ArgumentException) + { + modelRegex = null; + } + } + } + bool hideEverything = ImGui.Button("Hide Everything"); + ImGui.SameLine(); + bool showEverything = ImGui.Button("Show Everything"); + nint bodyArmorParts = MemoryUtil.Read<nint>(baseObject + 0x2A0); + if (bodyArmorParts != 0x0) + { + iterateList(bodyArmorParts - 0x40, (partsAddr) => + { + ImGui.PushID(partsAddr); + nint vTable = MemoryUtil.Read<nint>(partsAddr); + if (vTable == 0x143507A18) + { + string fullString = Marshal.PtrToStringAnsi(partsAddr + 0xC)!; + bool filterMatch = false; + if (modelFilterIsRegex) + { + filterMatch = modelRegex == null || modelRegex.Match(fullString).Success; + } + else + { + filterMatch = String.IsNullOrEmpty(modelFilter) || fullString.StartsWith(modelFilter); + } + if (filterMatch) + { + drawModelParts(0x0, partsAddr, hideEverything, showEverything); + } + } + ImGui.PopID(); + }); + } + } + ImGui.PopID(); + } + + private static void searchUnitList(nint addr, List<nint> objects) + { + nint baseAddr = MemoryUtil.Read<nint>(addr); + if (baseAddr == 0x0) return; + baseAddr = MemoryUtil.Read<nint>(baseAddr + 0xB8); + if (baseAddr == 0x0) return; + iterateList(baseAddr + 0x40, (objectAddr) => + { + nint vTable = MemoryUtil.Read<nint>(objectAddr); + if (vTable == 0x14341F728 || vTable == 0x1434A9720) // Instanced Model || uScrMotionModel + { + objects.Add(objectAddr); + } + }); + } + + public static void DrawObjectList(float width, Player player) + { + List<nint> objects = new List<nint>(); + unchecked + { + searchUnitList((nint)0x1450ECA20, objects); + searchUnitList((nint)0x1450EC980, objects); + } + /* + nint baseAddr = MemoryUtil.Read<nint>(0x14500DFC0); + baseAddr = MemoryUtil.Read<nint>(baseAddr + 0xA0 + 0x1490); + if (baseAddr == 0x0) return; + for (;;) + { + nint objectAddr = MemoryUtil.Read<nint>(baseAddr); + if (objectAddr == 0x0) break; + nint vTable = MemoryUtil.Read<nint>(objectAddr); + if (vTable == 0x1434A9720) + { + objects.Add(objectAddr); + } + baseAddr += 0x8; + } + */ + ImGui.PushID("Objects"); + ImGui.SetNextItemWidth(width * 0.55f); + if (ImGui.InputText("##Filter", ref objectFilter, 256)) + { + } + ImGui.Checkbox("Sort By Distance To Player", ref sortObjectsByDistanceToPlayer); + IEnumerable<nint> enumerator = objects; + if (sortObjectsByDistanceToPlayer) + { + enumerator = Enumerable.OrderBy(objects, (x) => x, Comparer<nint>.Create((x, y) => + { + Vector3 pos = player.Position; + Vector3 xPos = default, yPos = default; + nint vTableX = MemoryUtil.Read<nint>(x); + nint vTableY = MemoryUtil.Read<nint>(y); + if (vTableX == 0x1434A9720) + { + xPos = MemoryUtil.GetRef<Vector3>(x + 0x160); + } + else if (vTableX == 0x14341F728) + { + bool instanced = (MemoryUtil.GetRef<byte>(x + 0x2C8) & 0x10) != 0x10; + if (instanced) + { + xPos = MemoryUtil.GetRef<Vector3>(x + 0x500); + } + else + { + xPos = MemoryUtil.GetRef<Vector3>(x + 0x160); + } + } + if (vTableY == 0x1434A9720) + { + yPos = MemoryUtil.GetRef<Vector3>(y + 0x160); + } + else if (vTableY == 0x14341F728) + { + bool instanced = (MemoryUtil.GetRef<byte>(y + 0x2C8) & 0x10) != 0x10; + if (instanced) + { + yPos = MemoryUtil.GetRef<Vector3>(y + 0x500); + } + else + { + yPos = MemoryUtil.GetRef<Vector3>(y + 0x160); + } + } + float distX = Vector3.Distance(pos, xPos); + float distY = Vector3.Distance(pos, yPos); + if (distX < distY) return -1; + else if (distX > distY) return 1; + return 0; + })); + } + foreach (nint objectAddr in enumerator) + { + nint partsAddr = MemoryUtil.Read<nint>(objectAddr + 0x2A0); + string fullString = Marshal.PtrToStringAnsi(partsAddr + 0xC)!; + bool filterMatch = String.IsNullOrEmpty(objectFilter) || (!String.IsNullOrEmpty(fullString) && fullString.StartsWith(objectFilter)); + if (!filterMatch) + { + continue; + } + ImGui.PushID(objectAddr); + if (ImGui.CollapsingHeader($"{fullString} @ 0x{objectAddr:X}")) + { + nint vTable = MemoryUtil.Read<nint>(objectAddr); + if (vTable == 0x1434A9720) + { + ImGui.DragFloat3("Position", ref MemoryUtil.GetRef<Vector3>(objectAddr + 0x160), 0.5f); + continue; + } + bool update = MemoryUtil.GetRef<byte>(objectAddr + 0x540) == 0x1; + if (ImGui.Checkbox("Update", ref update)) + { + MemoryUtil.GetRef<byte>(objectAddr + 0x540) = ByteFlag(update); + } + ImGui.SameLine(); + bool instanced = (MemoryUtil.GetRef<byte>(objectAddr + 0x2C8) & 0x10) == 0x10; + if (ImGui.Checkbox("Instanced", ref instanced)) + { + if (instanced) + { + MemoryUtil.GetRef<byte>(objectAddr + 0x2C8) |= 0x10; + } + else + { + MemoryUtil.GetRef<byte>(objectAddr + 0x2C8) &= 0xF; + MemoryUtil.GetRef<Vector3>(objectAddr + 0x160) = MemoryUtil.GetRef<Vector3>(objectAddr + 0x500); + } + } + if (instanced) + { + ImGui.DragFloat3("Position", ref MemoryUtil.GetRef<Vector3>(objectAddr + 0x500), 0.5f); + int instanceCount = MemoryUtil.Read<int>(objectAddr + 0x4C0); + nint instances = MemoryUtil.Read<nint>(objectAddr + 0x4B8); + ImGui.Text($"Instances: {instanceCount} (0x{instances:X})"); + for (int i = 0; i < instanceCount; i++) + { + ImGui.PushID(instances); + ref Vector3 position = ref MemoryUtil.GetRef<Vector3>(instances + 0xF0); + ref Vector4 rotation = ref MemoryUtil.GetRef<Vector4>(instances + 0x100); + ref Vector3 scale = ref MemoryUtil.GetRef<Vector3>(instances + 0x110); + ImGui.DragFloat3("Position", ref position, 0.5f); + ImGui.SliderFloat4("Rotation", ref rotation, -1.0f, 1.0f); + if (DrawRotate("Rotate", rotation, out Vector4 result)) + { + rotation = result; + } + ImGui.DragFloat3("Scale", ref scale, 0.025f); + ImGui.Separator(); + ImGui.PopID(); + instances += 0x1D0; + } + } + else + { + ref Vector3 position = ref MemoryUtil.GetRef<Vector3>(objectAddr + 0x160); + ref Vector4 rotation = ref MemoryUtil.GetRef<Vector4>(objectAddr + 0x180); + ref Vector3 scale = ref MemoryUtil.GetRef<Vector3>(objectAddr + 0x170); + ImGui.DragFloat3("Position", ref position, 0.5f); + if (ImGui.Button("Move to Player")) + { + position = new Vector3(player.Position.X, player.Position.Y - 150.0f, player.Position.Z); + } + ImGui.SliderFloat4("Rotation", ref rotation, -1.0f, 1.0f); + if (DrawRotate("Rotate", rotation, out Vector4 result)) + { + rotation = result; + } + ImGui.DragFloat3("Scale", ref scale, 0.025f); + } + } + ImGui.PopID(); + } + ImGui.PopID(); + } + } +} |