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(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(partsAddr + 0xC8); nint baseOffset = MemoryUtil.Read(partsAddr + 0x408 + (0x8 * 0x1)); int numParts = MemoryUtil.Read(partsAddr + 0x548 + (0x4 * 0x1)); // Setting lodCount to 0 is probably the best way to completely hide a model. int lodCount = MemoryUtil.Read(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(baseOffset + (i * 0x4)); nint lod0 = baseLod + lodOffset * lodStructSize; bool isVisible = MemoryUtil.GetRef(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(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(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(lod); ref ushort padSub = ref MemoryUtil.GetRef(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 func) { nint next = baseAddr; nint head = next; nint prev = MemoryUtil.Read(next + 0x10); for (;next != 0x0;) { func(next + 0x40); if (head == prev) { next = MemoryUtil.Read(next + 0x10); } else { next = MemoryUtil.Read(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(baseObject + 0x2A0); if (bodyArmorParts != 0x0) { iterateList(bodyArmorParts - 0x40, (partsAddr) => { ImGui.PushID(partsAddr); nint vTable = MemoryUtil.Read(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 objects) { nint baseAddr = MemoryUtil.Read(addr); if (baseAddr == 0x0) return; baseAddr = MemoryUtil.Read(baseAddr + 0xB8); if (baseAddr == 0x0) return; iterateList(baseAddr + 0x40, (objectAddr) => { nint vTable = MemoryUtil.Read(objectAddr); if (vTable == 0x14341F728 || vTable == 0x1434A9720) // Instanced Model || uScrMotionModel { objects.Add(objectAddr); } }); } public static void DrawObjectList(float width, Player player) { List objects = new List(); unchecked { searchUnitList((nint)0x1450ECA20, objects); searchUnitList((nint)0x1450EC980, objects); } /* nint baseAddr = MemoryUtil.Read(0x14500DFC0); baseAddr = MemoryUtil.Read(baseAddr + 0xA0 + 0x1490); if (baseAddr == 0x0) return; for (;;) { nint objectAddr = MemoryUtil.Read(baseAddr); if (objectAddr == 0x0) break; nint vTable = MemoryUtil.Read(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 enumerator = objects; if (sortObjectsByDistanceToPlayer) { enumerator = Enumerable.OrderBy(objects, (x) => x, Comparer.Create((x, y) => { Vector3 pos = player.Position; Vector3 xPos = default, yPos = default; nint vTableX = MemoryUtil.Read(x); nint vTableY = MemoryUtil.Read(y); if (vTableX == 0x1434A9720) { xPos = MemoryUtil.GetRef(x + 0x160); } else if (vTableX == 0x14341F728) { bool instanced = (MemoryUtil.GetRef(x + 0x2C8) & 0x10) != 0x10; if (instanced) { xPos = MemoryUtil.GetRef(x + 0x500); } else { xPos = MemoryUtil.GetRef(x + 0x160); } } if (vTableY == 0x1434A9720) { yPos = MemoryUtil.GetRef(y + 0x160); } else if (vTableY == 0x14341F728) { bool instanced = (MemoryUtil.GetRef(y + 0x2C8) & 0x10) != 0x10; if (instanced) { yPos = MemoryUtil.GetRef(y + 0x500); } else { yPos = MemoryUtil.GetRef(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(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(objectAddr); if (vTable == 0x1434A9720) { ImGui.DragFloat3("Position", ref MemoryUtil.GetRef(objectAddr + 0x160), 0.5f); continue; } bool update = MemoryUtil.GetRef(objectAddr + 0x540) == 0x1; if (ImGui.Checkbox("Update", ref update)) { MemoryUtil.GetRef(objectAddr + 0x540) = ByteFlag(update); } ImGui.SameLine(); bool instanced = (MemoryUtil.GetRef(objectAddr + 0x2C8) & 0x10) == 0x10; if (ImGui.Checkbox("Instanced", ref instanced)) { if (instanced) { MemoryUtil.GetRef(objectAddr + 0x2C8) |= 0x10; } else { MemoryUtil.GetRef(objectAddr + 0x2C8) &= 0xF; MemoryUtil.GetRef(objectAddr + 0x160) = MemoryUtil.GetRef(objectAddr + 0x500); } } if (instanced) { ImGui.DragFloat3("Position", ref MemoryUtil.GetRef(objectAddr + 0x500), 0.5f); int instanceCount = MemoryUtil.Read(objectAddr + 0x4C0); nint instances = MemoryUtil.Read(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(instances + 0xF0); ref Vector4 rotation = ref MemoryUtil.GetRef(instances + 0x100); ref Vector3 scale = ref MemoryUtil.GetRef(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(objectAddr + 0x160); ref Vector4 rotation = ref MemoryUtil.GetRef(objectAddr + 0x180); ref Vector3 scale = ref MemoryUtil.GetRef(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(); } } }