summaryrefslogtreecommitdiff
path: root/Models.cs
diff options
context:
space:
mode:
authorAndrew Opalach <andrew@akon.city> 2026-10-09 13:13:59 -0400
committerAndrew Opalach <andrew@akon.city> 2026-10-09 13:13:59 -0400
commita66fbdfab71ece98223d257f3a6da7ab0339ee1f (patch)
tree5289018b0b7130d9509eff971b060d0699f71b92 /Models.cs
parent8abc6082c701084d505fa4fdf391a8cbf69e4fec (diff)
downloadNewCamera-a66fbdfab71ece98223d257f3a6da7ab0339ee1f.tar.gz
NewCamera-a66fbdfab71ece98223d257f3a6da7ab0339ee1f.tar.bz2
NewCamera-a66fbdfab71ece98223d257f3a6da7ab0339ee1f.zip
Bunch of wip stuffHEADmaster
Signed-off-by: Andrew Opalach <andrew@akon.city>
Diffstat (limited to 'Models.cs')
-rw-r--r--Models.cs576
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();
+ }
+ }
+}